diff --git a/.cargo/config b/.cargo/config.toml similarity index 99% rename from .cargo/config rename to .cargo/config.toml index c81c668c82..5704896780 100644 --- a/.cargo/config +++ b/.cargo/config.toml @@ -8,14 +8,14 @@ rustflags = [ "-Zshare-generics=y" ] # [target.x86_64-unknown-linux-gnu] # linker = "clang" # rustflags = [ "-Clink-arg=-fuse-ld=lld" ] -# +# # # `brew install llvm` # [target.x86_64-apple-darwin] # rustflags = [ # "-C", # "link-arg=-fuse-ld=/usr/local/opt/llvm/bin/ld64.lld", # ] -# +# # [target.aarch64-apple-darwin] # rustflags = [ # "-C", diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 53ecc2da1e..8156a6934b 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -48,7 +48,7 @@ RUN apt-get install -y \ containerd.io \ docker-buildx-plugin -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --profile minimal --default-toolchain nightly-2022-10-29 -y +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --profile minimal --default-toolchain nightly-2023-06-01 -y RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protoc-3.20.1-linux-x86_64.zip RUN unzip protoc-3.20.1-linux-x86_64.zip && mv ./include/google /usr/include/google diff --git a/.docker/Dockerfile.ci-container b/.docker/Dockerfile.ci-container index d68cecd225..c56e686888 100644 --- a/.docker/Dockerfile.ci-container +++ b/.docker/Dockerfile.ci-container @@ -14,9 +14,14 @@ RUN apt-get install -y \ gnupg \ git \ zip \ + python3 \ + python3-pip \ + python-dev \ + libffi-dev \ sudo -RUN ln -s /usr/bin/python3 /bin/python +RUN pip3 install --upgrade pip +RUN pip3 install paramiko RUN apt install -y \ software-properties-common \ diff --git a/.docker/Dockerfile.dev-release b/.docker/Dockerfile.dev-release index 3167c38a59..74abcb02e3 100644 --- a/.docker/Dockerfile.dev-release +++ b/.docker/Dockerfile.dev-release @@ -1,5 +1,6 @@ FROM docker.io/debian:stable-slim -WORKDIR /mm2 +WORKDIR /kdf +COPY target/release/kdf /usr/local/bin/kdf COPY target/release/mm2 /usr/local/bin/mm2 EXPOSE 7783 -CMD ["mm2"] +CMD ["kdf"] diff --git a/.docker/Dockerfile.release b/.docker/Dockerfile.release index 8de75301d6..d9d8d51325 100644 --- a/.docker/Dockerfile.release +++ b/.docker/Dockerfile.release @@ -1,5 +1,6 @@ FROM docker.io/debian:stable-slim -WORKDIR /mm2 +WORKDIR /kdf +COPY target/release/kdf /usr/local/bin/kdf COPY target/release/mm2 /usr/local/bin/mm2 EXPOSE 7783 -CMD ["mm2"] \ No newline at end of file +CMD ["kdf"] \ No newline at end of file diff --git a/.docker/Dockerfile.ubuntu.ci b/.docker/Dockerfile.ubuntu.ci index 5e86dac8f2..23110a6761 100644 --- a/.docker/Dockerfile.ubuntu.ci +++ b/.docker/Dockerfile.ubuntu.ci @@ -18,4 +18,4 @@ RUN \ chmod -R 777 /root ENV PATH="/root/.cargo/bin:${PATH}" -WORKDIR /mm2 \ No newline at end of file +WORKDIR /kdf \ No newline at end of file diff --git a/.docker/container-state/atom-testnet-data/config/addrbook.json b/.docker/container-state/atom-testnet-data/config/addrbook.json new file mode 100644 index 0000000000..9f75ad40ef --- /dev/null +++ b/.docker/container-state/atom-testnet-data/config/addrbook.json @@ -0,0 +1,4 @@ +{ + "key": "9c1e658a8e070bbd7e7cfcaa", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/atom-testnet-data/config/app.toml b/.docker/container-state/atom-testnet-data/config/app.toml new file mode 100644 index 0000000000..af33d27cbf --- /dev/null +++ b/.docker/container-state/atom-testnet-data/config/app.toml @@ -0,0 +1,263 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +############################################################################### +### Base Configuration ### +############################################################################### + +# The minimum gas prices a validator is willing to accept for processing a +# transaction. A transaction's fees must meet the minimum of any denomination +# specified in this config (e.g. 0.25token1,0.0001token2). +minimum-gas-prices = "0.0025uatom" + +# default: the last 362880 states are kept, pruning at 10 block intervals +# nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node) +# everything: 2 latest states will be kept; pruning at 10 block intervals. +# custom: allow pruning options to be manually specified through 'pruning-keep-recent', and 'pruning-interval' +pruning = "default" + +# These are applied if and only if the pruning strategy is custom. +pruning-keep-recent = "0" +pruning-interval = "0" + +# HaltHeight contains a non-zero block height at which a node will gracefully +# halt and shutdown that can be used to assist upgrades and testing. +# +# Note: Commitment of state will be attempted on the corresponding block. +halt-height = 0 + +# HaltTime contains a non-zero minimum block time (in Unix seconds) at which +# a node will gracefully halt and shutdown that can be used to assist upgrades +# and testing. +# +# Note: Commitment of state will be attempted on the corresponding block. +halt-time = 0 + +# MinRetainBlocks defines the minimum block height offset from the current +# block being committed, such that all blocks past this offset are pruned +# from Tendermint. It is used as part of the process of determining the +# ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates +# that no blocks should be pruned. +# +# This configuration value is only responsible for pruning Tendermint blocks. +# It has no bearing on application state pruning which is determined by the +# "pruning-*" configurations. +# +# Note: Tendermint block pruning is dependant on this parameter in conunction +# with the unbonding (safety threshold) period, state pruning and state sync +# snapshot parameters to determine the correct minimum value of +# ResponseCommit.RetainHeight. +min-retain-blocks = 0 + +# InterBlockCache enables inter-block caching. +inter-block-cache = true + +# IndexEvents defines the set of events in the form {eventType}.{attributeKey}, +# which informs Tendermint what to index. If empty, all events will be indexed. +# +# Example: +# ["message.sender", "message.recipient"] +index-events = [] + +# IavlCacheSize set the size of the iavl tree cache (in number of nodes). +iavl-cache-size = 781250 + +# IAVLDisableFastNode enables or disables the fast node feature of IAVL. +# Default is false. +iavl-disable-fastnode = false + +# IAVLLazyLoading enable/disable the lazy loading of iavl store. +# Default is false. +iavl-lazy-loading = false + +# AppDBBackend defines the database backend type to use for the application and snapshots DBs. +# An empty string indicates that a fallback will be used. +# The fallback is the db_backend value set in Tendermint's config.toml. +app-db-backend = "" + +############################################################################### +### Telemetry Configuration ### +############################################################################### + +[telemetry] + +# Prefixed with keys to separate services. +service-name = "" + +# Enabled enables the application telemetry functionality. When enabled, +# an in-memory sink is also enabled by default. Operators may also enabled +# other sinks such as Prometheus. +enabled = false + +# Enable prefixing gauge values with hostname. +enable-hostname = false + +# Enable adding hostname to labels. +enable-hostname-label = false + +# Enable adding service to labels. +enable-service-label = false + +# PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. +prometheus-retention-time = 0 + +# GlobalLabels defines a global set of name/value label tuples applied to all +# metrics emitted using the wrapper functions defined in telemetry package. +# +# Example: +# [["chain_id", "cosmoshub-1"]] +global-labels = [ +] + +############################################################################### +### API Configuration ### +############################################################################### + +[api] + +# Enable defines if the API server should be enabled. +enable = true + +# Swagger defines if swagger documentation should automatically be registered. +swagger = true + +# Address defines the API server to listen on. +address = "tcp://localhost:1318" + +# MaxOpenConnections defines the number of maximum open connections. +max-open-connections = 1000 + +# RPCReadTimeout defines the Tendermint RPC read timeout (in seconds). +rpc-read-timeout = 10 + +# RPCWriteTimeout defines the Tendermint RPC write timeout (in seconds). +rpc-write-timeout = 0 + +# RPCMaxBodyBytes defines the Tendermint maximum request body (in bytes). +rpc-max-body-bytes = 1000000 + +# EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk). +enabled-unsafe-cors = false + +############################################################################### +### Rosetta Configuration ### +############################################################################### + +[rosetta] + +# Enable defines if the Rosetta API server should be enabled. +enable = false + +# Address defines the Rosetta API server to listen on. +address = ":8080" + +# Network defines the name of the blockchain that will be returned by Rosetta. +blockchain = "app" + +# Network defines the name of the network that will be returned by Rosetta. +network = "network" + +# Retries defines the number of retries when connecting to the node before failing. +retries = 3 + +# Offline defines if Rosetta server should run in offline mode. +offline = false + +# EnableDefaultSuggestedFee defines if the server should suggest fee by default. +# If 'construction/medata' is called without gas limit and gas price, +# suggested fee based on gas-to-suggest and denom-to-suggest will be given. +enable-fee-suggestion = false + +# GasToSuggest defines gas limit when calculating the fee +gas-to-suggest = 200000 + +# DenomToSuggest defines the defult denom for fee suggestion. +# Price must be in minimum-gas-prices. +denom-to-suggest = "uatom" + +############################################################################### +### gRPC Configuration ### +############################################################################### + +[grpc] + +# Enable defines if the gRPC server should be enabled. +enable = true + +# Address defines the gRPC server address to bind to. +address = "localhost:9090" + +# MaxRecvMsgSize defines the max message size in bytes the server can receive. +# The default value is 10MB. +max-recv-msg-size = "10485760" + +# MaxSendMsgSize defines the max message size in bytes the server can send. +# The default value is math.MaxInt32. +max-send-msg-size = "2147483647" + +############################################################################### +### gRPC Web Configuration ### +############################################################################### + +[grpc-web] + +# GRPCWebEnable defines if the gRPC-web should be enabled. +# NOTE: gRPC must also be enabled, otherwise, this configuration is a no-op. +enable = true + +# Address defines the gRPC-web server address to bind to. +address = "localhost:9091" + +# EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk). +enable-unsafe-cors = false + +############################################################################### +### State Sync Configuration ### +############################################################################### + +# State sync snapshots allow other nodes to rapidly join the network without replaying historical +# blocks, instead downloading and applying a snapshot of the application state at a given height. +[state-sync] + +# snapshot-interval specifies the block interval at which local state sync snapshots are +# taken (0 to disable). +snapshot-interval = 1000 + +# snapshot-keep-recent specifies the number of recent snapshots to keep and serve (0 to keep all). +snapshot-keep-recent = 10 + +############################################################################### +### Store / State Streaming ### +############################################################################### + +[store] +streamers = [] + +[streamers] +[streamers.file] +keys = ["*", ] +write_dir = "" +prefix = "" + +# output-metadata specifies if output the metadata file which includes the abci request/responses +# during processing the block. +output-metadata = "true" + +# stop-node-on-error specifies if propagate the file streamer errors to consensus state machine. +stop-node-on-error = "true" + +# fsync specifies if call fsync after writing the files. +fsync = "false" + +############################################################################### +### Mempool ### +############################################################################### + +[mempool] +# Setting max-txs to 0 will allow for a unbounded amount of transactions in the mempool. +# Setting max_txs to negative 1 (-1) will disable transactions from being inserted into the mempool. +# Setting max_txs to a positive number (> 0) will limit the number of transactions in the mempool, by the specified amount. +# +# Note, this configuration only applies to SDK built-in app-side mempool +# implementations. +max-txs = 5000 diff --git a/.docker/container-state/atom-testnet-data/config/client.toml b/.docker/container-state/atom-testnet-data/config/client.toml new file mode 100644 index 0000000000..a066773456 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/config/client.toml @@ -0,0 +1,17 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +############################################################################### +### Client Configuration ### +############################################################################### + +# The network chain ID +chain-id = "cosmoshub-testnet" +# The keyring's backend, where the keys are stored (os|file|kwallet|pass|test|memory) +keyring-backend = "test" +# CLI output format (text|json) +output = "text" +# : to Tendermint RPC interface for this chain +node = "tcp://0.0.0.0:26658" +# Transaction broadcasting mode (sync|async) +broadcast-mode = "sync" diff --git a/.docker/container-state/atom-testnet-data/config/config.toml b/.docker/container-state/atom-testnet-data/config/config.toml new file mode 100644 index 0000000000..31c8c4633a --- /dev/null +++ b/.docker/container-state/atom-testnet-data/config/config.toml @@ -0,0 +1,492 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +# NOTE: Any path below can be absolute (e.g. "/var/myawesomeapp/data") or +# relative to the home directory (e.g. "data"). The home directory is +# "$HOME/.cometbft" by default, but could be changed via $CMTHOME env variable +# or --home cmd flag. + +####################################################################### +### Main Base Config Options ### +####################################################################### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the CometBFT binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "nimda" + +# If this node is many blocks behind the tip of the chain, BlockSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +# +# Deprecated: this key will be removed and BlockSync will be enabled +# unconditionally in the next major release. +block_sync = true + +# Database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb +# * goleveldb (github.com/syndtr/goleveldb - most popular implementation) +# - pure go +# - stable +# * cleveldb (uses levigo wrapper) +# - fast +# - requires gcc +# - use cleveldb build tag (go build -tags cleveldb) +# * boltdb (uses etcd's fork of bolt - github.com/etcd-io/bbolt) +# - EXPERIMENTAL +# - may be faster is some use-cases (random reads - indexer) +# - use boltdb build tag (go build -tags boltdb) +# * rocksdb (uses github.com/tecbot/gorocksdb) +# - EXPERIMENTAL +# - requires gcc +# - use rocksdb build tag (go build -tags rocksdb) +# * badgerdb (uses github.com/dgraph-io/badger) +# - EXPERIMENTAL +# - use badgerdb build tag (go build -tags badgerdb) +db_backend = "goleveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "info" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_key_file = "config/priv_validator_key.json" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "data/priv_validator_state.json" + +# TCP or UNIX socket address for CometBFT to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + + +####################################################################### +### Advanced Configuration Options ### +####################################################################### + +####################################################### +### RPC Server Configuration Options ### +####################################################### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://0.0.0.0:26658" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +# Maximum number of unique clientIDs that can /subscribe +# If you're using /broadcast_tx_commit, set to the estimated maximum number +# of broadcast_tx_commit calls per block. +max_subscription_clients = 100 + +# Maximum number of unique queries a given client can /subscribe to +# If you're using GRPC (or Local RPC client) and /broadcast_tx_commit, set to +# the estimated # maximum number of broadcast_tx_commit calls per block. +max_subscriptions_per_client = 5 + +# Experimental parameter to specify the maximum number of events a node will +# buffer, per subscription, before returning an error and closing the +# subscription. Must be set to at least 100, but higher values will accommodate +# higher event throughput rates (and will use more memory). +experimental_subscription_buffer_size = 200 + +# Experimental parameter to specify the maximum number of RPC responses that +# can be buffered per WebSocket client. If clients cannot read from the +# WebSocket endpoint fast enough, they will be disconnected, so increasing this +# parameter may reduce the chances of them being disconnected (but will cause +# the node to use more memory). +# +# Must be at least the same as "experimental_subscription_buffer_size", +# otherwise connections could be dropped unnecessarily. This value should +# ideally be somewhat higher than "experimental_subscription_buffer_size" to +# accommodate non-subscription-related RPC responses. +experimental_websocket_write_buffer_size = 200 + +# If a WebSocket client cannot read fast enough, at present we may +# silently drop events instead of generating an error or disconnecting the +# client. +# +# Enabling this experimental parameter will cause the WebSocket connection to +# be closed instead if it cannot read fast enough, allowing for greater +# predictability in subscription behavior. +experimental_close_on_slow_client = false + +# How long to wait for a tx to be committed during /broadcast_tx_commit. +# WARNING: Using a value larger than 10s will result in increasing the +# global HTTP write timeout, which applies to all connections and endpoints. +# See https://github.com/tendermint/tendermint/issues/3435 +timeout_broadcast_tx_commit = "10s" + +# Maximum size of request body, in bytes +max_body_bytes = 1000000 + +# Maximum size of request header, in bytes +max_header_bytes = 1048576 + +# The path to a file containing certificate that is used to create the HTTPS server. +# Might be either absolute path or path related to CometBFT's config directory. +# If the certificate is signed by a certificate authority, +# the certFile should be the concatenation of the server's certificate, any intermediates, +# and the CA's certificate. +# NOTE: both tls_cert_file and tls_key_file must be present for CometBFT to create HTTPS server. +# Otherwise, HTTP server is run. +tls_cert_file = "" + +# The path to a file containing matching private key that is used to create the HTTPS server. +# Might be either absolute path or path related to CometBFT's config directory. +# NOTE: both tls-cert-file and tls-key-file must be present for CometBFT to create HTTPS server. +# Otherwise, HTTP server is run. +tls_key_file = "" + +# pprof listen address (https://golang.org/pkg/net/http/pprof) +pprof_laddr = "localhost:6061" + +####################################################### +### P2P Configuration Options ### +####################################################### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26655" + +# Address to advertise to peers for them to dial. If empty, will use the same +# port as the laddr, and will introspect on the listener to figure out the +# address. IP and port are required. Example: 159.89.10.97:26655 +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# List of node IDs, to which a connection will be (re)established ignoring any existing limits +unconditional_peer_ids = "" + +# Maximum pause when redialing a persistent peer (if zero, exponential backoff is used) +persistent_peers_max_dial_period = "0s" + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = false + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +####################################################### +### Mempool Configuration Option ### +####################################################### +[mempool] + +# Mempool version to use: +# 1) "v0" - (default) FIFO mempool. +# 2) "v1" - prioritized mempool (deprecated; will be removed in the next release). +version = "v0" + +# The type of mempool for this node to use. +# +# Possible types: +# - "flood" : concurrent linked list mempool with flooding gossip protocol +# (default) +# - "nop" : nop-mempool (short for no operation; the ABCI app is responsible +# for storing, disseminating and proposing txs). "create_empty_blocks=false" is +# not supported. +type = "flood" + +recheck = true +broadcast = true +wal_dir = "" + +# Maximum number of transactions in the mempool +size = 5000 + +# Limit the total size of all txs in the mempool. +# This only accounts for raw transactions (e.g. given 1MB transactions and +# max_txs_bytes=5MB, mempool will only accept 5 transactions). +max_txs_bytes = 1073741824 + +# Size of the cache (used to filter transactions we saw earlier) in transactions +cache_size = 10000 + +# Do not remove invalid transactions from the cache (default: false) +# Set to true if it's not possible for any invalid transaction to become valid +# again in the future. +keep-invalid-txs-in-cache = false + +# Maximum size of a single transaction. +# NOTE: the max size of a tx transmitted over the network is {max_tx_bytes}. +max_tx_bytes = 1048576 + +# Maximum size of a batch of transactions to send to a peer +# Including space needed by encoding (one varint per transaction). +# XXX: Unused due to https://github.com/tendermint/tendermint/issues/5796 +max_batch_bytes = 0 + +# ttl-duration, if non-zero, defines the maximum amount of time a transaction +# can exist for in the mempool. +# +# Note, if ttl-num-blocks is also defined, a transaction will be removed if it +# has existed in the mempool at least ttl-num-blocks number of blocks or if it's +# insertion time into the mempool is beyond ttl-duration. +ttl-duration = "0s" + +# ttl-num-blocks, if non-zero, defines the maximum number of blocks a transaction +# can exist for in the mempool. +# +# Note, if ttl-duration is also defined, a transaction will be removed if it +# has existed in the mempool at least ttl-num-blocks number of blocks or if +# it's insertion time into the mempool is beyond ttl-duration. +ttl-num-blocks = 0 + +# Experimental parameters to limit gossiping txs to up to the specified number of peers. +# This feature is only available for the default mempool (version config set to "v0"). +# We use two independent upper values for persistent and non-persistent peers. +# Unconditional peers are not affected by this feature. +# If we are connected to more than the specified number of persistent peers, only send txs to +# ExperimentalMaxGossipConnectionsToPersistentPeers of them. If one of those +# persistent peers disconnects, activate another persistent peer. +# Similarly for non-persistent peers, with an upper limit of +# ExperimentalMaxGossipConnectionsToNonPersistentPeers. +# If set to 0, the feature is disabled for the corresponding group of peers, that is, the +# number of active connections to that group of peers is not bounded. +# For non-persistent peers, if enabled, a value of 10 is recommended based on experimental +# performance results using the default P2P configuration. +experimental_max_gossip_connections_to_persistent_peers = 0 +experimental_max_gossip_connections_to_non_persistent_peers = 0 + +####################################################### +### State Sync Configuration Options ### +####################################################### +[statesync] +# State sync rapidly bootstraps a new node by discovering, fetching, and restoring a state machine +# snapshot from peers instead of fetching and replaying historical blocks. Requires some peers in +# the network to take and serve state machine snapshots. State sync is not attempted if the node +# has any local state (LastBlockHeight > 0). The node will have a truncated block history, +# starting from the height of the snapshot. +enable = false + +# RPC servers (comma-separated) for light client verification of the synced state machine and +# retrieval of state data for node bootstrapping. Also needs a trusted height and corresponding +# header hash obtained from a trusted source, and a period during which validators can be trusted. +# +# For Cosmos SDK-based chains, trust_period should usually be about 2/3 of the unbonding time (~2 +# weeks) during which they can be financially punished (slashed) for misbehavior. +rpc_servers = "" +trust_height = 0 +trust_hash = "" +trust_period = "168h0m0s" + +# Time to spend discovering snapshots before initiating a restore. +discovery_time = "15s" + +# Temporary directory for state sync snapshot chunks, defaults to the OS tempdir (typically /tmp). +# Will create a new, randomly named directory within, and remove it when done. +temp_dir = "" + +# The timeout duration before re-requesting a chunk, possibly from a different +# peer (default: 1 minute). +chunk_request_timeout = "10s" + +# The number of concurrent chunk fetchers to run (default: 1). +chunk_fetchers = "4" + +####################################################### +### Block Sync Configuration Options ### +####################################################### +[blocksync] + +# Block Sync version to use: +# +# In v0.37, v1 and v2 of the block sync protocols were deprecated. +# Please use v0 instead. +# +# 1) "v0" - the default block sync implementation +version = "v0" + +####################################################### +### Consensus Configuration Options ### +####################################################### +[consensus] + +wal_file = "data/cs.wal/wal" + +# How long we wait for a proposal block before prevoting nil +timeout_propose = "3s" +# How much timeout_propose increases with each round +timeout_propose_delta = "500ms" +# How long we wait after receiving +2/3 prevotes for “anything” (ie. not a single block or nil) +timeout_prevote = "1s" +# How much the timeout_prevote increases with each round +timeout_prevote_delta = "500ms" +# How long we wait after receiving +2/3 precommits for “anything” (ie. not a single block or nil) +timeout_precommit = "1s" +# How much the timeout_precommit increases with each round +timeout_precommit_delta = "500ms" +# How long we wait after committing a block, before starting on the new +# height (this gives us a chance to receive some more precommits, even +# though we already have +2/3). +timeout_commit = "5s" + +# How many blocks to look back to check existence of the node's consensus votes before joining consensus +# When non-zero, the node will panic upon restart +# if the same consensus key was used to sign {double_sign_check_height} last blocks. +# So, validators should stop the state machine, wait for some blocks, and then restart the state machine to avoid panic. +double_sign_check_height = 0 + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +####################################################### +### Storage Configuration Options ### +####################################################### +[storage] + +# Set to true to discard ABCI responses from the state store, which can save a +# considerable amount of disk space. Set to false to ensure ABCI responses are +# persisted. ABCI responses are required for /block_results RPC queries, and to +# reindex events in the command-line tool. +discard_abci_responses = false + +####################################################### +### Transaction Indexer Configuration Options ### +####################################################### +[tx_index] + +# What indexer to use for transactions +# +# The application will set which txs to index. In some cases a node operator will be able +# to decide which txs to index based on configuration set in the application. +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +# - When "kv" is chosen "tx.height" and "tx.hash" will always be indexed. +# 3) "psql" - the indexer services backed by PostgreSQL. +# When "kv" or "psql" is chosen "tx.height" and "tx.hash" will always be indexed. +indexer = "kv" + +# The PostgreSQL connection configuration, the connection format: +# postgresql://:@:/? +psql-conn = "" + +####################################################### +### Instrumentation Configuration Options ### +####################################################### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "cometbft" diff --git a/.docker/container-state/atom-testnet-data/config/genesis.json b/.docker/container-state/atom-testnet-data/config/genesis.json new file mode 100644 index 0000000000..c6f9d33015 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/config/genesis.json @@ -0,0 +1,544 @@ +{ + "genesis_time": "2024-06-03T11:43:33.263437606Z", + "chain_id": "cosmoshub-testnet", + "initial_height": "1", + "consensus_params": { + "block": { + "max_bytes": "22020096", + "max_gas": "-1" + }, + "evidence": { + "max_age_num_blocks": "100000", + "max_age_duration": "172800000000000", + "max_bytes": "1048576" + }, + "validator": { + "pub_key_types": [ + "ed25519" + ] + }, + "version": { + "app": "0" + } + }, + "app_hash": "", + "app_state": { + "07-tendermint": null, + "auth": { + "params": { + "max_memo_characters": "256", + "tx_sig_limit": "7", + "tx_size_cost_per_byte": "10", + "sig_verify_cost_ed25519": "590", + "sig_verify_cost_secp256k1": "1000" + }, + "accounts": [ + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "cosmos15d4sf4z6y0vk9dnum8yzkvr9c3wq4q89k6d4n0", + "pub_key": null, + "account_number": "0", + "sequence": "0" + }, + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "cosmos1yfygf0zr5s69ces9r0h72hqv23nkqz9nc2lvt2", + "pub_key": null, + "account_number": "1", + "sequence": "0" + }, + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "cosmos10tfc28dmn2m5qdrmg5ycjyqq7lyu7y8ledc8tc", + "pub_key": null, + "account_number": "2", + "sequence": "0" + }, + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "cosmos150evuj4j7k9kgu38e453jdv9m3u0ft2n53flg6", + "pub_key": null, + "account_number": "3", + "sequence": "0" + } + ] + }, + "authz": { + "authorization": [] + }, + "bank": { + "params": { + "send_enabled": [], + "default_send_enabled": true + }, + "balances": [ + { + "address": "cosmos1yfygf0zr5s69ces9r0h72hqv23nkqz9nc2lvt2", + "coins": [] + }, + { + "address": "cosmos10tfc28dmn2m5qdrmg5ycjyqq7lyu7y8ledc8tc", + "coins": [ + { + "denom": "uatom", + "amount": "12345" + } + ] + }, + { + "address": "cosmos15d4sf4z6y0vk9dnum8yzkvr9c3wq4q89k6d4n0", + "coins": [ + { + "denom": "uatom", + "amount": "10000000000" + } + ] + }, + { + "address": "cosmos150evuj4j7k9kgu38e453jdv9m3u0ft2n53flg6", + "coins": [ + { + "denom": "uatom", + "amount": "10000000000" + } + ] + } + ], + "supply": [ + { + "denom": "uatom", + "amount": "20000012345" + } + ], + "denom_metadata": [ + { + "description": "The native staking token of Cosmoshub.", + "denom_units": [ + { + "denom": "uatom", + "exponent": 0, + "aliases": [ + "microatom" + ] + }, + { + "denom": "matom", + "exponent": 3, + "aliases": [ + "milliatom" + ] + }, + { + "denom": "atom", + "exponent": 6, + "aliases": [] + } + ], + "base": "uatom", + "display": "atom", + "name": "", + "symbol": "", + "uri": "", + "uri_hash": "" + } + ], + "send_enabled": [] + }, + "capability": { + "index": "1", + "owners": [] + }, + "consensus": null, + "crisis": { + "constant_fee": { + "denom": "uatom", + "amount": "1000" + } + }, + "distribution": { + "params": { + "community_tax": "0.020000000000000000", + "base_proposer_reward": "0.000000000000000000", + "bonus_proposer_reward": "0.000000000000000000", + "withdraw_addr_enabled": true + }, + "fee_pool": { + "community_pool": [] + }, + "delegator_withdraw_infos": [], + "previous_proposer": "", + "outstanding_rewards": [], + "validator_accumulated_commissions": [], + "validator_historical_rewards": [], + "validator_current_rewards": [], + "delegator_starting_infos": [], + "validator_slash_events": [] + }, + "evidence": { + "evidence": [] + }, + "feegrant": { + "allowances": [] + }, + "feeibc": { + "identified_fees": [], + "fee_enabled_channels": [], + "registered_payees": [], + "registered_counterparty_payees": [], + "forward_relayers": [] + }, + "genutil": { + "gen_txs": [ + { + "body": { + "messages": [ + { + "@type": "/cosmos.staking.v1beta1.MsgCreateValidator", + "description": { + "moniker": "nimda", + "identity": "", + "website": "", + "security_contact": "", + "details": "" + }, + "commission": { + "rate": "0.050000000000000000", + "max_rate": "0.200000000000000000", + "max_change_rate": "0.010000000000000000" + }, + "min_self_delegation": "0", + "delegator_address": "cosmos15d4sf4z6y0vk9dnum8yzkvr9c3wq4q89k6d4n0", + "validator_address": "cosmosvaloper15d4sf4z6y0vk9dnum8yzkvr9c3wq4q89nweqlu", + "pubkey": { + "@type": "/cosmos.crypto.ed25519.PubKey", + "key": "jJu9A07VzP62GblYgmbBHed8DlmFNVg2jEaM0p6FLQU=" + }, + "value": { + "denom": "uatom", + "amount": "200000000" + } + } + ], + "memo": "07cf84bfb3d35e62409e6637f11edc414edd6f6b@192.168.1.24:26656", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AsgJRkBRhm9bOTsGtcByi+fBbdjEp6BB59AiFHDqDRN0" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_DIRECT" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [ + { + "denom": "uatom", + "amount": "2500" + } + ], + "gas_limit": "200000", + "payer": "", + "granter": "" + }, + "tip": null + }, + "signatures": [ + "RIr7bzYEV6f6LBAdzSwx7FrDg5Ya6HloX6su6p4jPWcxgAH9ChduQrEpSlsdEPgqQ31l2xjkQLeqqm3Hn6eCGA==" + ] + } + ] + }, + "globalfee": { + "params": { + "minimum_gas_prices": [], + "bypass_min_fee_msg_types": [ + "/ibc.core.channel.v1.MsgRecvPacket", + "/ibc.core.channel.v1.MsgAcknowledgement", + "/ibc.core.client.v1.MsgUpdateClient", + "/ibc.core.channel.v1.MsgTimeout", + "/ibc.core.channel.v1.MsgTimeoutOnClose" + ], + "max_total_bypass_min_fee_msg_gas_usage": "1000000" + } + }, + "gov": { + "starting_proposal_id": "1", + "deposits": [], + "votes": [], + "proposals": [], + "deposit_params": null, + "voting_params": null, + "tally_params": null, + "params": { + "min_deposit": [ + { + "denom": "uatom", + "amount": "10000000" + } + ], + "max_deposit_period": "172800s", + "voting_period": "172800s", + "quorum": "0.334000000000000000", + "threshold": "0.500000000000000000", + "veto_threshold": "0.334000000000000000", + "min_initial_deposit_ratio": "0.000000000000000000", + "burn_vote_quorum": false, + "burn_proposal_deposit_prevote": false, + "burn_vote_veto": true, + "min_deposit_ratio": "0.010000000000000000" + } + }, + "ibc": { + "client_genesis": { + "clients": [], + "clients_consensus": [], + "clients_metadata": [], + "params": { + "allowed_clients": [ + "06-solomachine", + "07-tendermint", + "09-localhost" + ] + }, + "create_localhost": false, + "next_client_sequence": "0" + }, + "connection_genesis": { + "connections": [], + "client_connection_paths": [], + "next_connection_sequence": "0", + "params": { + "max_expected_time_per_block": "30000000000" + } + }, + "channel_genesis": { + "channels": [], + "acknowledgements": [], + "commitments": [], + "receipts": [], + "send_sequences": [], + "recv_sequences": [], + "ack_sequences": [], + "next_channel_sequence": "0" + } + }, + "interchainaccounts": { + "controller_genesis_state": { + "active_channels": [], + "interchain_accounts": [], + "ports": [], + "params": { + "controller_enabled": true + } + }, + "host_genesis_state": { + "active_channels": [], + "interchain_accounts": [], + "port": "icahost", + "params": { + "host_enabled": true, + "allow_messages": [ + "*" + ] + } + } + }, + "metaprotocols": {}, + "mint": { + "minter": { + "inflation": "0.130000000000000000", + "annual_provisions": "0.000000000000000000" + }, + "params": { + "mint_denom": "uatom", + "inflation_rate_change": "0.130000000000000000", + "inflation_max": "0.200000000000000000", + "inflation_min": "0.070000000000000000", + "goal_bonded": "0.670000000000000000", + "blocks_per_year": "6311520" + } + }, + "packetfowardmiddleware": { + "params": { + "fee_percentage": "0.000000000000000000" + }, + "in_flight_packets": {} + }, + "params": null, + "provider": { + "valset_update_id": "1", + "consumer_states": [], + "unbonding_ops": [], + "mature_unbonding_ops": null, + "valset_update_id_to_height": [], + "consumer_addition_proposals": [], + "consumer_removal_proposals": [], + "params": { + "template_client": { + "chain_id": "", + "trust_level": { + "numerator": "1", + "denominator": "3" + }, + "trusting_period": "0s", + "unbonding_period": "0s", + "max_clock_drift": "10s", + "frozen_height": { + "revision_number": "0", + "revision_height": "0" + }, + "latest_height": { + "revision_number": "0", + "revision_height": "0" + }, + "proof_specs": [ + { + "leaf_spec": { + "hash": "SHA256", + "prehash_key": "NO_HASH", + "prehash_value": "SHA256", + "length": "VAR_PROTO", + "prefix": "AA==" + }, + "inner_spec": { + "child_order": [ + 0, + 1 + ], + "child_size": 33, + "min_prefix_length": 4, + "max_prefix_length": 12, + "empty_child": null, + "hash": "SHA256" + }, + "max_depth": 0, + "min_depth": 0, + "prehash_key_before_comparison": false + }, + { + "leaf_spec": { + "hash": "SHA256", + "prehash_key": "NO_HASH", + "prehash_value": "SHA256", + "length": "VAR_PROTO", + "prefix": "AA==" + }, + "inner_spec": { + "child_order": [ + 0, + 1 + ], + "child_size": 32, + "min_prefix_length": 1, + "max_prefix_length": 1, + "empty_child": null, + "hash": "SHA256" + }, + "max_depth": 0, + "min_depth": 0, + "prehash_key_before_comparison": false + } + ], + "upgrade_path": [ + "upgrade", + "upgradedIBCState" + ], + "allow_update_after_expiry": false, + "allow_update_after_misbehaviour": false + }, + "trusting_period_fraction": "0.66", + "ccv_timeout_period": "2419200s", + "init_timeout_period": "604800s", + "vsc_timeout_period": "3024000s", + "slash_meter_replenish_period": "3600s", + "slash_meter_replenish_fraction": "0.05", + "consumer_reward_denom_registration_fee": { + "denom": "uatom", + "amount": "10000000" + }, + "blocks_per_epoch": "600" + }, + "validator_consumer_pubkeys": [], + "validators_by_consumer_addr": [], + "consumer_addrs_to_prune": [], + "init_timeout_timestamps": [], + "exported_vsc_send_timestamps": [] + }, + "ratelimit": { + "params": {}, + "rate_limits": [], + "whitelisted_address_pairs": [], + "blacklisted_denoms": [], + "pending_send_packet_sequence_numbers": [], + "hour_epoch": { + "epoch_number": "0", + "duration": "3600s", + "epoch_start_time": "0001-01-01T00:00:00Z", + "epoch_start_height": "0" + } + }, + "slashing": { + "params": { + "signed_blocks_window": "100", + "min_signed_per_window": "0.500000000000000000", + "downtime_jail_duration": "600s", + "slash_fraction_double_sign": "0.050000000000000000", + "slash_fraction_downtime": "0.010000000000000000" + }, + "signing_infos": [], + "missed_blocks": [] + }, + "staking": { + "params": { + "unbonding_time": "1814400s", + "max_validators": 100, + "max_entries": 7, + "historical_entries": 10000, + "bond_denom": "uatom", + "min_commission_rate": "0.000000000000000000", + "validator_bond_factor": "-1.000000000000000000", + "global_liquid_staking_cap": "1.000000000000000000", + "validator_liquid_staking_cap": "1.000000000000000000" + }, + "last_total_power": "0", + "last_validator_powers": [], + "validators": [], + "delegations": [], + "unbonding_delegations": [], + "redelegations": [], + "exported": false, + "tokenize_share_records": [], + "last_tokenize_share_record_id": "0", + "tokenize_share_locks": [] + }, + "transfer": { + "port_id": "transfer", + "denom_traces": [], + "params": { + "send_enabled": true, + "receive_enabled": true + }, + "total_escrowed": [] + }, + "upgrade": {}, + "vesting": {}, + "htlc": { + "params": { + "asset_params": [] + }, + "htlcs": [], + "supplies": [], + "previous_block_time": "1970-01-01T00:00:01Z" + } + } +} \ No newline at end of file diff --git a/.docker/container-state/atom-testnet-data/config/gentx/gentx-07cf84bfb3d35e62409e6637f11edc414edd6f6b.json b/.docker/container-state/atom-testnet-data/config/gentx/gentx-07cf84bfb3d35e62409e6637f11edc414edd6f6b.json new file mode 100644 index 0000000000..13879cb222 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/config/gentx/gentx-07cf84bfb3d35e62409e6637f11edc414edd6f6b.json @@ -0,0 +1 @@ +{"body":{"messages":[{"@type":"/cosmos.staking.v1beta1.MsgCreateValidator","description":{"moniker":"nimda","identity":"","website":"","security_contact":"","details":""},"commission":{"rate":"0.050000000000000000","max_rate":"0.200000000000000000","max_change_rate":"0.010000000000000000"},"min_self_delegation":"0","delegator_address":"cosmos15d4sf4z6y0vk9dnum8yzkvr9c3wq4q89k6d4n0","validator_address":"cosmosvaloper15d4sf4z6y0vk9dnum8yzkvr9c3wq4q89nweqlu","pubkey":{"@type":"/cosmos.crypto.ed25519.PubKey","key":"jJu9A07VzP62GblYgmbBHed8DlmFNVg2jEaM0p6FLQU="},"value":{"denom":"uatom","amount":"200000000"}}],"memo":"07cf84bfb3d35e62409e6637f11edc414edd6f6b@192.168.1.24:26656","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AsgJRkBRhm9bOTsGtcByi+fBbdjEp6BB59AiFHDqDRN0"},"mode_info":{"single":{"mode":"SIGN_MODE_DIRECT"}},"sequence":"0"}],"fee":{"amount":[{"denom":"uatom","amount":"2500"}],"gas_limit":"200000","payer":"","granter":""},"tip":null},"signatures":["RIr7bzYEV6f6LBAdzSwx7FrDg5Ya6HloX6su6p4jPWcxgAH9ChduQrEpSlsdEPgqQ31l2xjkQLeqqm3Hn6eCGA=="]} diff --git a/.docker/container-state/atom-testnet-data/config/node_key.json b/.docker/container-state/atom-testnet-data/config/node_key.json new file mode 100644 index 0000000000..4e2f33b9fb --- /dev/null +++ b/.docker/container-state/atom-testnet-data/config/node_key.json @@ -0,0 +1 @@ +{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"fFxhUeVktUmntyAYZqCpOnV4LdkXCgV1x+516mKwOY9PIQQAxxZvRUZiWMNdpcFVV7U9q8XoIrbi0oH9EOU6IQ=="}} \ No newline at end of file diff --git a/.docker/container-state/atom-testnet-data/config/priv_validator_key.json b/.docker/container-state/atom-testnet-data/config/priv_validator_key.json new file mode 100644 index 0000000000..2b76bcb996 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/config/priv_validator_key.json @@ -0,0 +1,11 @@ +{ + "address": "3E49B197AC395DB2BD999B4C3F35F1B6B36508D0", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "jJu9A07VzP62GblYgmbBHed8DlmFNVg2jEaM0p6FLQU=" + }, + "priv_key": { + "type": "tendermint/PrivKeyEd25519", + "value": "/FmlJVv/qhnR0lfYXgngRFR2C5jA1NtNElR4xa0N28eMm70DTtXM/rYZuViCZsEd53wOWYU1WDaMRozSnoUtBQ==" + } +} \ No newline at end of file diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000145.ldb b/.docker/container-state/atom-testnet-data/data/application.db/000145.ldb new file mode 100644 index 0000000000..ffa94a328e Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/application.db/000145.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000146.ldb b/.docker/container-state/atom-testnet-data/data/application.db/000146.ldb new file mode 100644 index 0000000000..305c23033d Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/application.db/000146.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000147.ldb b/.docker/container-state/atom-testnet-data/data/application.db/000147.ldb new file mode 100644 index 0000000000..4c895dfd75 Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/application.db/000147.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000148.ldb b/.docker/container-state/atom-testnet-data/data/application.db/000148.ldb new file mode 100644 index 0000000000..44b0a978d0 Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/application.db/000148.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000149.ldb b/.docker/container-state/atom-testnet-data/data/application.db/000149.ldb new file mode 100644 index 0000000000..a79ab5bddf Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/application.db/000149.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000150.log b/.docker/container-state/atom-testnet-data/data/application.db/000150.log new file mode 100644 index 0000000000..19f3ee4d7e Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/application.db/000150.log differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/CURRENT b/.docker/container-state/atom-testnet-data/data/application.db/CURRENT new file mode 100644 index 0000000000..3f137c99d1 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/application.db/CURRENT @@ -0,0 +1 @@ +MANIFEST-000151 diff --git a/.docker/container-state/atom-testnet-data/data/application.db/CURRENT.bak b/.docker/container-state/atom-testnet-data/data/application.db/CURRENT.bak new file mode 100644 index 0000000000..3e77273f6e --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/application.db/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000144 diff --git a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs b/.docker/container-state/atom-testnet-data/data/application.db/LOCK similarity index 100% rename from mm2src/mm2_main/tests/mm2_tests/iris_swap.rs rename to .docker/container-state/atom-testnet-data/data/application.db/LOCK diff --git a/.docker/container-state/atom-testnet-data/data/application.db/LOG b/.docker/container-state/atom-testnet-data/data/application.db/LOG new file mode 100644 index 0000000000..0e35591395 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/application.db/LOG @@ -0,0 +1,548 @@ +=============== Jun 3, 2024 (+03) =============== +14:44:32.470783 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +14:44:32.472928 db@open opening +14:44:32.473060 version@stat F·[] S·0B[] Sc·[] +14:44:32.473634 db@janitor F·2 G·0 +14:44:32.473644 db@open done T·708.125µs +=============== Jun 6, 2024 (+03) =============== +18:04:49.030249 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:04:49.030388 version@stat F·[] S·0B[] Sc·[] +18:04:49.030400 db@open opening +18:04:49.030434 journal@recovery F·1 +18:04:49.030820 journal@recovery recovering @1 +18:04:49.038914 memdb@flush created L0@2 N·8801 S·458KiB "s/1,v430":"s/p..hts,v590" +18:04:49.039037 version@stat F·[1] S·458KiB[458KiB] Sc·[0.25] +18:04:49.041764 db@janitor F·3 G·0 +18:04:49.041784 db@open done T·11.378459ms +=============== Jun 6, 2024 (+03) =============== +18:09:26.383098 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:09:26.383228 version@stat F·[1] S·458KiB[458KiB] Sc·[0.25] +18:09:26.383240 db@open opening +18:09:26.383267 journal@recovery F·1 +18:09:26.385208 journal@recovery recovering @3 +18:09:26.393123 memdb@flush created L0@5 N·8851 S·474KiB "s/100,v15842":"s/p..hts,v8954" +18:09:26.393259 version@stat F·[2] S·932KiB[932KiB] Sc·[0.50] +18:09:26.396002 db@janitor F·4 G·0 +18:09:26.396016 db@open done T·12.770381ms +18:09:31.452362 table@compaction L0·2 -> L1·0 S·932KiB Q·17654 +18:09:31.458728 table@build created L1@8 N·13251 S·882KiB "s/1,v430":"s/p..hts,v17651" +18:09:31.458755 version@stat F·[0 1] S·882KiB[0B 882KiB] Sc·[0.00 0.01] +18:09:31.459341 table@compaction committed F-1 S-49KiB Ke·0 D·4401 T·6.95821ms +18:09:31.459478 table@remove removed @2 +=============== Jun 6, 2024 (+03) =============== +19:18:28.670453 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +19:18:28.670587 version@stat F·[0 1] S·882KiB[0B 882KiB] Sc·[0.00 0.01] +19:18:28.670603 db@open opening +19:18:28.670631 journal@recovery F·1 +19:18:28.670706 journal@recovery recovering @6 +19:18:28.675997 memdb@flush created L0@9 N·4727 S·258KiB "s/112,v17816":"s/p..hts,v17815" +19:18:28.676113 version@stat F·[1 1] S·1MiB[258KiB 882KiB] Sc·[0.25 0.01] +19:18:28.678710 db@janitor F·5 G·1 +19:18:28.678716 db@janitor removing table-5 +19:18:28.678854 db@open done T·8.247273ms +=============== Jun 6, 2024 (+03) =============== +19:29:57.913638 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +19:29:57.913762 version@stat F·[1 1] S·1MiB[258KiB 882KiB] Sc·[0.25 0.01] +19:29:57.913776 db@open opening +19:29:57.913805 journal@recovery F·1 +19:29:57.913883 journal@recovery recovering @10 +19:29:57.915623 memdb@flush created L0@12 N·756 S·39KiB "s/139,v22557":"s/p..hts,v22556" +19:29:57.915929 version@stat F·[2 1] S·1MiB[297KiB 882KiB] Sc·[0.50 0.01] +19:29:57.918802 db@janitor F·5 G·0 +19:29:57.918815 db@open done T·5.034214ms +19:30:02.970800 table@compaction L0·2 -> L1·1 S·1MiB Q·23139 +19:30:02.979924 table@build created L1@15 N·17454 S·1MiB "s/1,v430":"s/p..hts,v23136" +19:30:02.979953 version@stat F·[0 1] S·1MiB[0B 1MiB] Sc·[0.00 0.01] +19:30:02.980537 table@compaction committed F-2 S-4KiB Ke·0 D·1280 T·9.713575ms +19:30:02.980645 table@remove removed @9 +19:30:02.980828 table@remove removed @8 +=============== Jun 10, 2024 (UTC) =============== +10:09:48.888722 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:09:48.888843 version@stat F·[0 1] S·1MiB[0B 1MiB] Sc·[0.00 0.01] +10:09:48.888855 db@open opening +10:09:48.888888 journal@recovery F·1 +10:09:48.889077 journal@recovery recovering @13 +10:09:48.891277 memdb@flush created L0@16 N·1329 S·68KiB "s/143,v23304":"s/p..hts,v23303" +10:09:48.891428 version@stat F·[1 1] S·1MiB[68KiB 1MiB] Sc·[0.25 0.01] +10:09:48.894794 db@janitor F·5 G·1 +10:09:48.894800 db@janitor removing table-12 +10:09:48.894839 db@open done T·5.979788ms +10:10:19.001548 table@compaction L0·1 -> L1·1 S·1MiB Q·25336 +10:10:19.010338 table@build created L1@19 N·18470 S·1MiB "s/1,v430":"s/p..hts,v24466" +10:10:19.010358 version@stat F·[0 1] S·1MiB[0B 1MiB] Sc·[0.00 0.01] +10:10:19.010930 table@compaction committed F-1 S+1KiB Ke·0 D·313 T·9.363036ms +10:10:19.011217 table@remove removed @15 +=============== Jun 10, 2024 (UTC) =============== +10:19:47.864242 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:19:47.864404 version@stat F·[0 1] S·1MiB[0B 1MiB] Sc·[0.00 0.01] +10:19:47.864417 db@open opening +10:19:47.864455 journal@recovery F·1 +10:19:47.864543 journal@recovery recovering @17 +10:19:47.883750 memdb@flush created L0@20 N·19720 S·1MiB "s/151,v24659":"s/p..hts,v24658" +10:19:47.883883 version@stat F·[1 1] S·2MiB[1MiB 1MiB] Sc·[0.25 0.01] +10:19:47.886887 db@janitor F·5 G·1 +10:19:47.886895 db@janitor removing table-16 +10:19:47.886941 db@open done T·22.519639ms +=============== Jun 10, 2024 (UTC) =============== +10:20:11.739618 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:20:11.739758 version@stat F·[1 1] S·2MiB[1MiB 1MiB] Sc·[0.25 0.01] +10:20:11.739793 db@open opening +10:20:11.739833 journal@recovery F·1 +10:20:11.739918 journal@recovery recovering @21 +10:20:11.740384 version@stat F·[1 1] S·2MiB[1MiB 1MiB] Sc·[0.25 0.01] +10:20:11.743444 db@janitor F·4 G·0 +10:20:11.743466 db@open done T·3.657641ms +10:21:01.860122 table@compaction L0·1 -> L1·1 S·2MiB Q·45701 +10:21:01.873375 table@build created L1@25 N·28916 S·2MiB "s/1,v430":"s/k..D\xaa\x83,v20141" +10:21:01.875580 table@build created L1@26 N·4609 S·215KiB "s/k..\x06\vf,v1010":"s/p..hts,v44187" +10:21:01.875601 version@stat F·[0 2] S·2MiB[0B 2MiB] Sc·[0.00 0.02] +10:21:01.876175 table@compaction committed F~ S-46KiB Ke·0 D·4665 T·16.036575ms +10:21:01.876420 table@remove removed @20 +10:21:01.876646 table@remove removed @19 +=============== Jun 10, 2024 (UTC) =============== +10:23:28.279773 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:23:28.279915 version@stat F·[0 2] S·2MiB[0B 2MiB] Sc·[0.00 0.02] +10:23:28.279928 db@open opening +10:23:28.279963 journal@recovery F·1 +10:23:28.280049 journal@recovery recovering @23 +10:23:28.286016 memdb@flush created L0@27 N·5710 S·307KiB "s/268,v44357":"s/p..hts,v44356" +10:23:28.286191 version@stat F·[1 2] S·2MiB[307KiB 2MiB] Sc·[0.25 0.02] +10:23:28.289566 db@janitor F·5 G·0 +10:23:28.289579 db@open done T·9.646421ms +10:24:13.402908 table@compaction L0·1 -> L1·2 S·2MiB Q·51243 +10:24:13.416567 table@build created L1@30 N·30507 S·2MiB "s/1,v430":"s/k..6L|,v16959" +10:24:13.420157 table@build created L1@31 N·7398 S·526KiB "s/k..\x9e\x02\xd1,v7074":"s/p..hts,v49898" +10:24:13.420180 version@stat F·[0 2] S·2MiB[0B 2MiB] Sc·[0.00 0.03] +10:24:13.420757 table@compaction committed F-1 S+4KiB Ke·0 D·1330 T·17.82593ms +10:24:13.421167 table@remove removed @25 +10:24:13.421239 table@remove removed @26 +=============== Jun 10, 2024 (UTC) =============== +10:27:08.453702 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:27:08.453825 version@stat F·[0 2] S·2MiB[0B 2MiB] Sc·[0.00 0.03] +10:27:08.453837 db@open opening +10:27:08.453874 journal@recovery F·1 +10:27:08.455857 journal@recovery recovering @28 +10:27:08.461852 memdb@flush created L0@32 N·7019 S·388KiB "s/302,v50066":"s/p..hts,v50065" +10:27:08.462783 version@stat F·[1 2] S·2MiB[388KiB 2MiB] Sc·[0.25 0.03] +10:27:08.465641 db@janitor F·6 G·1 +10:27:08.465655 db@janitor removing table-27 +10:27:08.465750 db@open done T·11.90829ms +10:28:18.630807 table@compaction L0·1 -> L1·2 S·2MiB Q·59266 +10:28:18.645204 table@build created L1@35 N·32525 S·2MiB "s/1,v430":"s/k..\xe8\x0fr,v31522" +10:28:18.650417 table@build created L1@36 N·10781 S·895KiB "s/k..\x1fE\xf7,v42971":"s/p..hts,v56918" +10:28:18.650437 version@stat F·[0 2] S·2MiB[0B 2MiB] Sc·[0.00 0.03] +10:28:18.651013 table@compaction committed F-1 S-18KiB Ke·0 D·1618 T·20.181509ms +10:28:18.651419 table@remove removed @30 +10:28:18.651531 table@remove removed @31 +=============== Jun 10, 2024 (UTC) =============== +10:30:28.072433 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:30:28.072563 version@stat F·[0 2] S·2MiB[0B 2MiB] Sc·[0.00 0.03] +10:30:28.072576 db@open opening +10:30:28.072619 journal@recovery F·1 +10:30:28.072705 journal@recovery recovering @33 +10:30:28.076122 memdb@flush created L0@37 N·3354 S·181KiB "s/343,v57088":"s/p..hts,v57087" +10:30:28.076417 version@stat F·[1 2] S·3MiB[181KiB 2MiB] Sc·[0.25 0.03] +10:30:28.080919 db@janitor F·6 G·1 +10:30:28.080934 db@janitor removing table-32 +10:30:28.081042 db@open done T·8.460381ms +10:31:13.198757 table@compaction L0·1 -> L1·2 S·3MiB Q·61619 +10:31:13.212967 table@build created L1@40 N·33817 S·2MiB "s/1,v430":"s/k..101,v15999" +10:31:13.219101 table@build created L1@41 N·12061 S·1MiB "s/k..102,v16164":"s/p..hts,v60273" +10:31:13.219122 version@stat F·[0 2] S·3MiB[0B 3MiB] Sc·[0.00 0.03] +10:31:13.219696 table@compaction committed F-1 S+1KiB Ke·0 D·782 T·20.920426ms +10:31:13.220094 table@remove removed @35 +10:31:13.220302 table@remove removed @36 +=============== Jun 10, 2024 (UTC) =============== +11:11:44.643599 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:11:44.643714 version@stat F·[0 2] S·3MiB[0B 3MiB] Sc·[0.00 0.03] +11:11:44.643726 db@open opening +11:11:44.643777 journal@recovery F·1 +11:11:44.643861 journal@recovery recovering @38 +11:11:44.652096 memdb@flush created L0@42 N·8401 S·461KiB "s/363,v60443":"s/p..hts,v60442" +11:11:44.654048 version@stat F·[1 2] S·3MiB[461KiB 3MiB] Sc·[0.25 0.03] +11:11:44.656876 db@janitor F·6 G·1 +11:11:44.656883 db@janitor removing table-37 +11:11:44.656946 db@open done T·13.21517ms +11:12:14.752800 table@compaction L0·1 -> L1·2 S·3MiB Q·69618 +11:12:14.767156 table@build created L1@45 N·33052 S·2MiB "s/1,v430":"s/k..!%\x05,v33355" +11:12:14.776213 table@build created L1@46 N·19272 S·1MiB "s/k..A.f,v33358":"s/p..hts,v68675" +11:12:14.776266 version@stat F·[0 2] S·3MiB[0B 3MiB] Sc·[0.00 0.04] +11:12:14.778223 table@compaction committed F-1 S-19KiB Ke·0 D·1955 T·25.395841ms +11:12:14.778633 table@remove removed @40 +11:12:14.778843 table@remove removed @41 +=============== Jun 10, 2024 (UTC) =============== +11:15:00.429927 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:15:00.430050 version@stat F·[0 2] S·3MiB[0B 3MiB] Sc·[0.00 0.04] +11:15:00.430063 db@open opening +11:15:00.430099 journal@recovery F·1 +11:15:00.430194 journal@recovery recovering @43 +11:15:00.434598 memdb@flush created L0@47 N·4626 S·267KiB "s/413,v68845":"s/p..hts,v68844" +11:15:00.435140 version@stat F·[1 2] S·3MiB[267KiB 3MiB] Sc·[0.25 0.04] +11:15:00.438447 db@janitor F·6 G·1 +11:15:00.438463 db@janitor removing table-42 +11:15:00.438584 db@open done T·8.515761ms +=============== Jun 10, 2024 (UTC) =============== +11:21:08.263340 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:21:08.263473 version@stat F·[1 2] S·3MiB[267KiB 3MiB] Sc·[0.25 0.04] +11:21:08.263485 db@open opening +11:21:08.263520 journal@recovery F·1 +11:21:08.263603 journal@recovery recovering @48 +11:21:08.264212 version@stat F·[1 2] S·3MiB[267KiB 3MiB] Sc·[0.25 0.04] +11:21:08.266901 db@janitor F·5 G·0 +11:21:08.266919 db@open done T·3.422808ms +=============== Jun 10, 2024 (UTC) =============== +11:22:28.199114 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:22:28.199268 version@stat F·[1 2] S·3MiB[267KiB 3MiB] Sc·[0.25 0.04] +11:22:28.199281 db@open opening +11:22:28.199319 journal@recovery F·1 +11:22:28.199409 journal@recovery recovering @50 +11:22:28.200482 memdb@flush created L0@52 N·174 S·10KiB "s/437,v73478":"s/p..hts,v73477" +11:22:28.200769 version@stat F·[2 2] S·3MiB[277KiB 3MiB] Sc·[0.50 0.04] +11:22:28.204055 db@janitor F·6 G·0 +11:22:28.204075 db@open done T·4.78312ms +11:22:33.277462 table@compaction L0·2 -> L1·2 S·3MiB Q·73480 +11:22:33.291989 table@build created L1@55 N·32831 S·2MiB "s/1,v430":"s/k..\xca\xec\x89,v36389" +11:22:33.302726 table@build created L1@56 N·23270 S·1MiB "s/k..\x82<\x01,v33691":"s/p..hts,v73477" +11:22:33.302751 version@stat F·[0 2] S·3MiB[0B 3MiB] Sc·[0.00 0.04] +11:22:33.303326 table@compaction committed F-2 S-5KiB Ke·0 D·1023 T·25.824435ms +11:22:33.303436 table@remove removed @47 +11:22:33.303808 table@remove removed @45 +11:22:33.304122 table@remove removed @46 +=============== Jun 10, 2024 (UTC) =============== +11:23:15.294049 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:23:15.294165 version@stat F·[0 2] S·3MiB[0B 3MiB] Sc·[0.00 0.04] +11:23:15.294178 db@open opening +11:23:15.294219 journal@recovery F·1 +11:23:15.294309 journal@recovery recovering @53 +11:23:15.295725 memdb@flush created L0@57 N·871 S·46KiB "s/438,v73653":"s/p..hts,v73652" +11:23:15.295980 version@stat F·[1 2] S·3MiB[46KiB 3MiB] Sc·[0.25 0.04] +11:23:15.299082 db@janitor F·6 G·1 +11:23:15.299096 db@janitor removing table-52 +11:23:15.299134 db@open done T·4.952161ms +11:23:50.404822 table@compaction L0·1 -> L1·2 S·3MiB Q·75396 +11:23:50.419850 table@build created L1@60 N·32875 S·2MiB "s/1,v430":"s/k..\x93\xe0],v32335" +11:23:50.430534 table@build created L1@61 N·23901 S·1MiB "s/k..\x13<\xea,v17425":"s/p..hts,v74349" +11:23:50.430556 version@stat F·[0 2] S·3MiB[0B 3MiB] Sc·[0.00 0.04] +11:23:50.431136 table@compaction committed F-1 S+1KiB Ke·0 D·196 T·26.29764ms +11:23:50.431536 table@remove removed @55 +11:23:50.431867 table@remove removed @56 +=============== Jun 10, 2024 (UTC) =============== +11:29:08.654580 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:29:08.654713 version@stat F·[0 2] S·3MiB[0B 3MiB] Sc·[0.00 0.04] +11:29:08.654727 db@open opening +11:29:08.654764 journal@recovery F·1 +11:29:08.654851 journal@recovery recovering @58 +11:29:08.665426 memdb@flush created L0@62 N·11434 S·635KiB "s/443,v74525":"s/p..hts,v74524" +11:29:08.665566 version@stat F·[1 2] S·4MiB[635KiB 3MiB] Sc·[0.25 0.04] +11:29:08.668438 db@janitor F·6 G·1 +11:29:08.668448 db@janitor removing table-57 +11:29:08.668492 db@open done T·13.759515ms +11:29:53.787682 table@compaction L0·1 -> L1·2 S·4MiB Q·87164 +11:29:53.802279 table@build created L1@65 N·30758 S·2MiB "s/1,v430":"s/k..\xd3\x01\xbd,v36705" +11:29:53.814393 table@build created L1@66 N·25657 S·2MiB "s/k..\x00\xe8\xc7,v36704":"s/k..t[\xd5,v10496" +11:29:53.818240 table@build created L1@67 N·9215 S·408KiB "s/k..\x1fa\x1c,v3122":"s/p..hts,v85784" +11:29:53.818266 version@stat F·[0 3] S·4MiB[0B 4MiB] Sc·[0.00 0.04] +11:29:53.818845 table@compaction committed F~ S-3KiB Ke·0 D·2580 T·31.139681ms +11:29:53.819258 table@remove removed @60 +11:29:53.819625 table@remove removed @61 +=============== Jun 10, 2024 (UTC) =============== +11:30:42.338644 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:42.338765 version@stat F·[0 3] S·4MiB[0B 4MiB] Sc·[0.00 0.04] +11:30:42.338779 db@open opening +11:30:42.338817 journal@recovery F·1 +11:30:42.339033 journal@recovery recovering @63 +11:30:42.342593 memdb@flush created L0@68 N·2936 S·159KiB "s/509,v85960":"s/p..hts,v85959" +11:30:42.342747 version@stat F·[1 3] S·4MiB[159KiB 4MiB] Sc·[0.25 0.04] +11:30:42.345611 db@janitor F·7 G·1 +11:30:42.345622 db@janitor removing table-62 +11:30:42.345772 db@open done T·6.987419ms +=============== Jun 10, 2024 (UTC) =============== +11:30:55.600264 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:55.600395 version@stat F·[1 3] S·4MiB[159KiB 4MiB] Sc·[0.25 0.04] +11:30:55.600410 db@open opening +11:30:55.600449 journal@recovery F·1 +11:30:55.600584 journal@recovery recovering @69 +11:30:55.602155 memdb@flush created L0@71 N·174 S·10KiB "s/526,v88897":"s/p..hts,v88896" +11:30:55.602318 version@stat F·[2 3] S·4MiB[169KiB 4MiB] Sc·[0.50 0.04] +11:30:55.604943 db@janitor F·7 G·0 +11:30:55.604959 db@open done T·4.543228ms +11:31:00.674202 table@compaction L0·2 -> L1·3 S·4MiB Q·88899 +11:31:00.688956 table@build created L1@74 N·30690 S·2MiB "s/1,v430":"s/k..\x9aNH,v23211" +11:31:00.701699 table@build created L1@75 N·26683 S·2MiB "s/k..\xf7\xfcj,v78951":"s/k..T\xea(,v68803" +11:31:00.706363 table@build created L1@76 N·10663 S·569KiB "s/k..4\xaa\x9c,v83159":"s/p..hts,v88896" +11:31:00.706391 version@stat F·[0 3] S·4MiB[0B 4MiB] Sc·[0.00 0.05] +11:31:00.706972 table@compaction committed F-2 S-9KiB Ke·0 D·704 T·32.741325ms +11:31:00.707066 table@remove removed @68 +11:31:00.707443 table@remove removed @65 +11:31:00.707913 table@remove removed @66 +11:31:00.708008 table@remove removed @67 +=============== Jun 10, 2024 (UTC) =============== +11:36:36.118534 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:36:36.118658 version@stat F·[0 3] S·4MiB[0B 4MiB] Sc·[0.00 0.05] +11:36:36.118672 db@open opening +11:36:36.118712 journal@recovery F·1 +11:36:36.118796 journal@recovery recovering @72 +11:36:36.128330 memdb@flush created L0@77 N·10436 S·584KiB "s/527,v89072":"s/p..hts,v89071" +11:36:36.128457 version@stat F·[1 3] S·5MiB[584KiB 4MiB] Sc·[0.25 0.05] +11:36:36.131274 db@janitor F·7 G·1 +11:36:36.131284 db@janitor removing table-71 +11:36:36.131323 db@open done T·12.644866ms +11:37:16.245879 table@compaction L0·1 -> L1·3 S·5MiB Q·100545 +11:37:16.260038 table@build created L1@80 N·28695 S·2MiB "s/1,v430":"s/k..\x96g\xfe,v88424" +11:37:16.274075 table@build created L1@81 N·31724 S·2MiB "s/k..\xb4\xc9-,v88423":"s/k..\x14f\xab,v88687" +11:37:16.281200 table@build created L1@82 N·15707 S·1MiB "s/k..\xc0\x14\xad,v59055":"s/p..hts,v99333" +11:37:16.281223 version@stat F·[0 3] S·5MiB[0B 5MiB] Sc·[0.00 0.05] +11:37:16.281805 table@compaction committed F-1 S-18KiB Ke·0 D·2346 T·35.907911ms +11:37:16.282217 table@remove removed @74 +11:37:16.282629 table@remove removed @75 +11:37:16.282802 table@remove removed @76 +=============== Jun 10, 2024 (UTC) =============== +12:01:05.009469 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:01:05.009641 version@stat F·[0 3] S·5MiB[0B 5MiB] Sc·[0.00 0.05] +12:01:05.009656 db@open opening +12:01:05.009696 journal@recovery F·1 +12:01:05.009781 journal@recovery recovering @78 +12:01:05.018430 memdb@flush created L0@83 N·8756 S·488KiB "s/587,v99509":"s/p..hts,v99508" +12:01:05.018561 version@stat F·[1 3] S·5MiB[488KiB 5MiB] Sc·[0.25 0.05] +12:01:05.021399 db@janitor F·7 G·1 +12:01:05.021409 db@janitor removing table-77 +12:01:05.021553 db@open done T·11.890965ms +=============== Jun 10, 2024 (UTC) =============== +12:03:00.000946 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:00.001079 version@stat F·[1 3] S·5MiB[488KiB 5MiB] Sc·[0.25 0.05] +12:03:00.001093 db@open opening +12:03:00.001133 journal@recovery F·1 +12:03:00.001225 journal@recovery recovering @84 +12:03:00.002416 memdb@flush created L0@86 N·531 S·28KiB "s/637,v108268":"s/p..hts,v108267" +12:03:00.002624 version@stat F·[2 3] S·5MiB[516KiB 5MiB] Sc·[0.50 0.05] +12:03:00.005937 db@janitor F·7 G·0 +12:03:00.005952 db@open done T·4.853053ms +12:03:05.077109 table@compaction L0·2 -> L1·3 S·5MiB Q·108625 +12:03:05.091524 table@build created L1@89 N·27160 S·2MiB "s/1,v430":"s/k..p\xf6%,v38370" +12:03:05.107522 table@build created L1@90 N·35970 S·2MiB "s/k..\xcc8$,v38365":"s/k..\x89\xee\x89,v19481" +12:03:05.117642 table@build created L1@91 N·20210 S·1MiB "s/k..\xa4u-,v36431":"s/p..hts,v108622" +12:03:05.117667 version@stat F·[0 3] S·5MiB[0B 5MiB] Sc·[0.00 0.06] +12:03:05.118245 table@compaction committed F-2 S-11KiB Ke·0 D·2073 T·41.114952ms +12:03:05.118396 table@remove removed @83 +12:03:05.118803 table@remove removed @80 +12:03:05.119233 table@remove removed @81 +12:03:05.119442 table@remove removed @82 +=============== Jun 10, 2024 (UTC) =============== +12:03:19.709632 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:19.709765 version@stat F·[0 3] S·5MiB[0B 5MiB] Sc·[0.00 0.06] +12:03:19.709779 db@open opening +12:03:19.709820 journal@recovery F·1 +12:03:19.709955 journal@recovery recovering @87 +12:03:19.711380 memdb@flush created L0@92 N·347 S·18KiB "s/640,v108797":"s/p..hts,v108795" +12:03:19.713412 version@stat F·[1 3] S·5MiB[18KiB 5MiB] Sc·[0.25 0.06] +12:03:19.716005 db@janitor F·7 G·1 +12:03:19.716015 db@janitor removing table-86 +12:03:19.716059 db@open done T·6.274876ms +=============== Jun 10, 2024 (UTC) =============== +12:04:23.803627 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:04:23.803757 version@stat F·[1 3] S·5MiB[18KiB 5MiB] Sc·[0.25 0.06] +12:04:23.803771 db@open opening +12:04:23.803811 journal@recovery F·1 +12:04:23.804004 journal@recovery recovering @93 +12:04:23.805335 memdb@flush created L0@95 N·174 S·10KiB "s/642,v109146":"s/p..hts,v109145" +12:04:23.805498 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:04:23.808108 db@janitor F·7 G·0 +12:04:23.808123 db@open done T·4.347018ms +=============== Jun 10, 2024 (UTC) =============== +12:07:46.886704 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:07:46.886839 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:07:46.886859 db@open opening +12:07:46.886900 journal@recovery F·1 +12:07:46.887029 journal@recovery recovering @96 +12:07:46.888107 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:07:46.890947 db@janitor F·7 G·0 +12:07:46.890962 db@open done T·4.097266ms +=============== Jun 10, 2024 (UTC) =============== +12:08:02.108588 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:08:02.108714 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:08:02.108729 db@open opening +12:08:02.108769 journal@recovery F·1 +12:08:02.108858 journal@recovery recovering @98 +12:08:02.109121 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:08:02.111934 db@janitor F·7 G·0 +12:08:02.111955 db@open done T·3.214158ms +=============== Jun 10, 2024 (UTC) =============== +12:27:21.433921 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:27:21.434059 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:27:21.434073 db@open opening +12:27:21.434110 journal@recovery F·1 +12:27:21.434202 journal@recovery recovering @100 +12:27:21.434741 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:27:21.438483 db@janitor F·7 G·0 +12:27:21.438506 db@open done T·4.420938ms +=============== Jun 10, 2024 (UTC) =============== +12:56:43.807711 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:56:43.807846 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:56:43.807861 db@open opening +12:56:43.807901 journal@recovery F·1 +12:56:43.808190 journal@recovery recovering @102 +12:56:43.809050 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:56:43.811721 db@janitor F·7 G·0 +12:56:43.811735 db@open done T·3.867954ms +=============== Jun 10, 2024 (UTC) =============== +12:57:38.382438 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:57:38.382574 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:57:38.382588 db@open opening +12:57:38.382631 journal@recovery F·1 +12:57:38.382875 journal@recovery recovering @104 +12:57:38.383858 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:57:38.386695 db@janitor F·7 G·0 +12:57:38.386709 db@open done T·4.114956ms +=============== Jun 10, 2024 (UTC) =============== +12:58:01.472246 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:01.472393 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:58:01.472411 db@open opening +12:58:01.472470 journal@recovery F·1 +12:58:01.472580 journal@recovery recovering @106 +12:58:01.472931 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:58:01.476286 db@janitor F·7 G·0 +12:58:01.476305 db@open done T·3.882034ms +=============== Jun 10, 2024 (UTC) =============== +12:58:48.995014 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:48.995158 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:58:48.995172 db@open opening +12:58:48.995220 journal@recovery F·1 +12:58:48.995307 journal@recovery recovering @108 +12:58:48.995915 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:58:48.998938 db@janitor F·7 G·0 +12:58:48.998959 db@open done T·3.773962ms +=============== Jun 10, 2024 (UTC) =============== +12:59:05.131412 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:05.131553 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:59:05.131566 db@open opening +12:59:05.131605 journal@recovery F·1 +12:59:05.131693 journal@recovery recovering @110 +12:59:05.132151 version@stat F·[2 3] S·5MiB[29KiB 5MiB] Sc·[0.50 0.06] +12:59:05.135904 db@janitor F·7 G·0 +12:59:05.135925 db@open done T·4.347538ms +12:59:10.211730 table@compaction L0·2 -> L1·3 S·5MiB Q·109148 +12:59:10.226056 table@build created L1@114 N·27001 S·2MiB "s/1,v430":"s/k..\xe0\xbe\x02,v34482" +12:59:10.243823 table@build created L1@115 N·36275 S·2MiB "s/k..8\xac\x87,v34652":"s/k..{OD,v100676" +12:59:10.253723 table@build created L1@116 N·20467 S·1MiB "s/k..\x87\xe7\xf7,v34071":"s/p..hts,v109145" +12:59:10.253746 version@stat F·[0 3] S·5MiB[0B 5MiB] Sc·[0.00 0.06] +12:59:10.254320 table@compaction committed F-2 S+2KiB Ke·0 D·118 T·42.56914ms +12:59:10.254386 table@remove removed @95 +12:59:10.254424 table@remove removed @92 +12:59:10.254797 table@remove removed @89 +12:59:10.255281 table@remove removed @90 +12:59:10.255568 table@remove removed @91 +=============== Jun 10, 2024 (UTC) =============== +12:59:32.406943 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:32.407078 version@stat F·[0 3] S·5MiB[0B 5MiB] Sc·[0.00 0.06] +12:59:32.407091 db@open opening +12:59:32.407127 journal@recovery F·1 +12:59:32.407218 journal@recovery recovering @112 +12:59:32.408386 memdb@flush created L0@117 N·176 S·10KiB "s/643,v109323":"s/p..hts,v109322" +12:59:32.408771 version@stat F·[1 3] S·5MiB[10KiB 5MiB] Sc·[0.25 0.06] +12:59:32.413762 db@janitor F·6 G·0 +12:59:32.413783 db@open done T·6.679628ms +=============== Jul 3, 2024 (UTC) =============== +17:52:58.343546 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:52:58.343680 version@stat F·[1 3] S·5MiB[10KiB 5MiB] Sc·[0.25 0.06] +17:52:58.343692 db@open opening +17:52:58.343730 journal@recovery F·1 +17:52:58.343813 journal@recovery recovering @118 +17:52:58.344805 version@stat F·[1 3] S·5MiB[10KiB 5MiB] Sc·[0.25 0.06] +17:52:58.347468 db@janitor F·6 G·0 +17:52:58.347493 db@open done T·3.787981ms +17:53:38.463406 table@compaction L0·1 -> L1·3 S·5MiB Q·110576 +17:53:38.476640 table@build created L1@122 N·27036 S·2MiB "s/1,v430":"s/k..\xe7\x85\xfc,v34490" +17:53:38.490737 table@build created L1@123 N·36290 S·2MiB "s/k..\x05u\xf4,v34491":"s/k..PX7,v35580" +17:53:38.499972 table@build created L1@124 N·20554 S·1MiB "s/k..\xef<&,v26322":"s/p..hts,v109322" +17:53:38.499994 version@stat F·[0 3] S·5MiB[0B 5MiB] Sc·[0.00 0.06] +17:53:38.500573 table@compaction committed F-1 S+769B Ke·0 D·39 T·37.149557ms +17:53:38.500641 table@remove removed @117 +17:53:38.501044 table@remove removed @114 +17:53:38.501495 table@remove removed @115 +17:53:38.501847 table@remove removed @116 +=============== Jul 3, 2024 (UTC) =============== +17:59:01.697660 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:59:01.697817 version@stat F·[0 3] S·5MiB[0B 5MiB] Sc·[0.00 0.06] +17:59:01.697829 db@open opening +17:59:01.697866 journal@recovery F·1 +17:59:01.697956 journal@recovery recovering @120 +17:59:01.710172 memdb@flush created L0@125 N·12904 S·739KiB "s/644,v109500":"s/p..hts,v109499" +17:59:01.710322 version@stat F·[1 3] S·6MiB[739KiB 5MiB] Sc·[0.25 0.06] +17:59:01.715180 db@janitor F·6 G·0 +17:59:01.715196 db@open done T·17.361833ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.858576 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.858710 version@stat F·[1 3] S·6MiB[739KiB 5MiB] Sc·[0.25 0.06] +05:10:40.858723 db@open opening +05:10:40.858766 journal@recovery F·1 +05:10:40.858847 journal@recovery recovering @126 +05:10:40.860765 memdb@flush created L0@128 N·905 S·48KiB "s/713,v122410":"s/p..hts,v122409" +05:10:40.862727 version@stat F·[2 3] S·6MiB[788KiB 5MiB] Sc·[0.50 0.06] +05:10:40.865397 db@janitor F·7 G·0 +05:10:40.865410 db@open done T·6.680542ms +05:10:40.972856 table@compaction L0·2 -> L1·3 S·6MiB Q·123136 +05:10:40.990144 table@build created L1@131 N·26043 S·2MiB "s/1,v430":"s/k..Z\xe2w,v101980" +05:10:41.012132 table@build created L1@132 N·39970 S·2MiB "s/k..\x8e\xd2\x7f,v43561":"s/k..\x86S\xad,v103471" +05:10:41.027607 table@build created L1@133 N·18990 S·2MiB "s/k..\x82\xb9\x82,v24251":"s/k..\x1a\xf6y,v40651" +05:10:41.034441 table@build created L1@134 N·9675 S·376KiB "s/k..\x9b\xb8\x1d,v40639":"s/p..hts,v123133" +05:10:41.034500 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.06] +05:10:41.035116 table@compaction committed F-1 S-28KiB Ke·0 D·3011 T·62.239547ms +05:10:41.035304 table@remove removed @125 +05:10:41.035687 table@remove removed @122 +05:10:41.036200 table@remove removed @123 +05:10:41.036511 table@remove removed @124 +=============== Oct 17, 2024 (UTC) =============== +06:44:58.533830 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.533977 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.06] +06:44:58.533988 db@open opening +06:44:58.534025 journal@recovery F·1 +06:44:58.534163 journal@recovery recovering @129 +06:44:58.588185 memdb@flush created L0@135 N·26580 S·1MiB "s/718,v123316":"s/p..hts,v123315" +06:44:58.588321 version@stat F·[1 4] S·7MiB[1MiB 6MiB] Sc·[0.25 0.06] +06:44:58.609803 db@janitor F·8 G·1 +06:44:58.609810 db@janitor removing table-128 +06:44:58.609853 db@open done T·75.860223ms +06:45:28.811214 table@compaction L0·1 -> L1·4 S·7MiB Q·150786 +06:45:28.852586 table@build created L1@138 N·26523 S·2MiB "s/1,v430":"s/k..\xfaW\xa8,v68382" +06:45:28.897007 table@build created L1@139 N·36709 S·2MiB "s/k..>Uu,v62163":"s/k..\x00\x01\xa4,v70055" +06:45:28.935643 table@build created L1@140 N·27416 S·2MiB "s/k..\x00\x01\xa5,v70226":"s/k..i\xf8\x8a,v22133" +06:45:28.974020 table@build created L1@141 N·24774 S·1MiB "s/k..e^\xef,v12056":"s/p..hts,v149714" +06:45:28.974046 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:45:28.981030 table@compaction committed F-1 S-26KiB Ke·0 D·5836 T·169.796253ms +06:45:28.981494 table@remove removed @131 +06:45:28.981958 table@remove removed @132 +06:45:28.982402 table@remove removed @133 +06:45:28.982499 table@remove removed @134 +=============== Oct 17, 2024 (UTC) =============== +06:45:46.585282 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.585428 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:45:46.585445 db@open opening +06:45:46.585484 journal@recovery F·1 +06:45:46.585567 journal@recovery recovering @136 +06:45:46.587472 memdb@flush created L0@142 N·1427 S·80KiB "s/862,v149897":"s/p..hts,v149896" +06:45:46.588280 version@stat F·[1 4] S·7MiB[80KiB 7MiB] Sc·[0.25 0.08] +06:45:46.591569 db@janitor F·8 G·1 +06:45:46.591580 db@janitor removing table-135 +06:45:46.591873 db@open done T·6.422715ms +06:46:51.810714 table@compaction L0·1 -> L1·4 S·7MiB Q·153293 +06:46:51.853844 table@build created L1@145 N·26596 S·2MiB "s/1,v430":"s/k..R\xa9',v46917" +06:46:51.895701 table@build created L1@146 N·36341 S·2MiB "s/k..\xc8A\xba,v114547":"s/k..\xa2*\xbf,v100466" +06:46:51.941201 table@build created L1@147 N·28140 S·2MiB "s/k..\xee^\xb5,v100467":"s/k..\xabk`,v122744" +06:46:51.976205 table@build created L1@148 N·25470 S·1MiB "s/k..̵\xd4,v106125":"s/p..hts,v151142" +06:46:51.976239 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:46:51.983945 table@compaction committed F-1 S-1KiB Ke·0 D·302 T·173.208828ms +06:46:51.984409 table@remove removed @138 +06:46:51.984907 table@remove removed @139 +06:46:51.985406 table@remove removed @140 +06:46:51.985884 table@remove removed @141 +=============== Oct 17, 2024 (UTC) =============== +06:47:26.207760 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.207883 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:47:26.207895 db@open opening +06:47:26.207934 journal@recovery F·1 +06:47:26.208025 journal@recovery recovering @143 +06:47:26.210939 memdb@flush created L0@149 N·2685 S·147KiB "s/869,v151323":"s/p..hts,v151322" +06:47:26.211271 version@stat F·[1 4] S·8MiB[147KiB 7MiB] Sc·[0.25 0.08] +06:47:26.214624 db@janitor F·8 G·1 +06:47:26.214639 db@janitor removing table-142 +06:47:26.214702 db@open done T·6.802848ms diff --git a/.docker/container-state/atom-testnet-data/data/application.db/MANIFEST-000151 b/.docker/container-state/atom-testnet-data/data/application.db/MANIFEST-000151 new file mode 100644 index 0000000000..12bea3a66a Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/application.db/MANIFEST-000151 differ diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/000109.ldb b/.docker/container-state/atom-testnet-data/data/blockstore.db/000109.ldb new file mode 100644 index 0000000000..f850f5a1ef Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/blockstore.db/000109.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/000110.ldb b/.docker/container-state/atom-testnet-data/data/blockstore.db/000110.ldb new file mode 100644 index 0000000000..6587bbe2bb Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/blockstore.db/000110.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/000111.log b/.docker/container-state/atom-testnet-data/data/blockstore.db/000111.log new file mode 100644 index 0000000000..0b0f0c9313 Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/blockstore.db/000111.log differ diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/CURRENT b/.docker/container-state/atom-testnet-data/data/blockstore.db/CURRENT new file mode 100644 index 0000000000..b59a6ba248 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/blockstore.db/CURRENT @@ -0,0 +1 @@ +MANIFEST-000112 diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/CURRENT.bak b/.docker/container-state/atom-testnet-data/data/blockstore.db/CURRENT.bak new file mode 100644 index 0000000000..db6fa61f96 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/blockstore.db/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000108 diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/LOCK b/.docker/container-state/atom-testnet-data/data/blockstore.db/LOCK new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/LOG b/.docker/container-state/atom-testnet-data/data/blockstore.db/LOG new file mode 100644 index 0000000000..8d007b4ffd --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/blockstore.db/LOG @@ -0,0 +1,440 @@ +=============== Jun 3, 2024 (+03) =============== +14:44:32.488873 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +14:44:32.490713 db@open opening +14:44:32.490847 version@stat F·[] S·0B[] Sc·[] +14:44:32.491453 db@janitor F·2 G·0 +14:44:32.491467 db@open done T·748.076µs +=============== Jun 6, 2024 (+03) =============== +18:04:49.061128 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:04:49.061216 version@stat F·[] S·0B[] Sc·[] +18:04:49.061227 db@open opening +18:04:49.061250 journal@recovery F·1 +18:04:49.061332 journal@recovery recovering @1 +18:04:49.062551 memdb@flush created L0@2 N·336 S·51KiB "BH:..f4e,v333":"blo..ore,v6" +18:04:49.062842 version@stat F·[1] S·51KiB[51KiB] Sc·[0.25] +18:04:49.066134 db@janitor F·3 G·0 +18:04:49.066148 db@open done T·4.916763ms +=============== Jun 6, 2024 (+03) =============== +18:09:26.413084 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:09:26.413194 version@stat F·[1] S·51KiB[51KiB] Sc·[0.25] +18:09:26.413205 db@open opening +18:09:26.413234 journal@recovery F·1 +18:09:26.413325 journal@recovery recovering @3 +18:09:26.414515 memdb@flush created L0@5 N·330 S·52KiB "BH:..e15,v628":"blo..ore,v343" +18:09:26.414886 version@stat F·[2] S·104KiB[104KiB] Sc·[0.50] +18:09:26.417663 db@janitor F·4 G·0 +18:09:26.417674 db@open done T·4.464969ms +=============== Jun 6, 2024 (+03) =============== +19:18:28.698110 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +19:18:28.698209 version@stat F·[2] S·104KiB[104KiB] Sc·[0.50] +19:18:28.698222 db@open opening +19:18:28.698250 journal@recovery F·1 +19:18:28.698341 journal@recovery recovering @6 +19:18:28.699364 memdb@flush created L0@8 N·162 S·27KiB "BH:..67b,v821":"blo..ore,v674" +19:18:28.699631 version@stat F·[3] S·132KiB[132KiB] Sc·[0.75] +19:18:28.702337 db@janitor F·5 G·0 +19:18:28.702348 db@open done T·4.123446ms +=============== Jun 6, 2024 (+03) =============== +19:29:57.938543 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +19:29:57.938619 version@stat F·[3] S·132KiB[132KiB] Sc·[0.75] +19:29:57.938629 db@open opening +19:29:57.938657 journal@recovery F·1 +19:29:57.938742 journal@recovery recovering @9 +19:29:57.939513 memdb@flush created L0@11 N·24 S·3KiB "BH:..6a1,v846":"blo..ore,v837" +19:29:57.939637 version@stat F·[4] S·135KiB[135KiB] Sc·[1.00] +19:29:57.942112 db@janitor F·6 G·0 +19:29:57.942119 db@open done T·3.487601ms +19:29:57.942146 table@compaction L0·4 -> L1·0 S·135KiB Q·856 +19:29:57.945014 table@build created L1@14 N·711 S·134KiB "BH:..e15,v628":"blo..ore,v855" +19:29:57.945035 version@stat F·[0 1] S·134KiB[0B 134KiB] Sc·[0.00 0.00] +19:29:57.945615 table@compaction committed F-3 S-1007B Ke·0 D·141 T·3.45401ms +19:29:57.945672 table@remove removed @8 +19:29:57.945706 table@remove removed @5 +19:29:57.945736 table@remove removed @2 +=============== Jun 10, 2024 (UTC) =============== +10:09:48.936150 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:09:48.936310 version@stat F·[0 1] S·134KiB[0B 134KiB] Sc·[0.00 0.00] +10:09:48.936325 db@open opening +10:09:48.936357 journal@recovery F·1 +10:09:48.936452 journal@recovery recovering @12 +10:09:48.937368 memdb@flush created L0@15 N·48 S·7KiB "BH:..6c1,v889":"blo..ore,v862" +10:09:48.937516 version@stat F·[1 1] S·141KiB[7KiB 134KiB] Sc·[0.25 0.00] +10:09:48.940107 db@janitor F·5 G·1 +10:09:48.940114 db@janitor removing table-11 +10:09:48.940160 db@open done T·3.831502ms +=============== Jun 10, 2024 (UTC) =============== +10:19:47.919317 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:19:47.919437 version@stat F·[1 1] S·141KiB[7KiB 134KiB] Sc·[0.25 0.00] +10:19:47.919453 db@open opening +10:19:47.919481 journal@recovery F·1 +10:19:47.919558 journal@recovery recovering @16 +10:19:47.921218 memdb@flush created L0@18 N·702 S·109KiB "BH:..c56,v1346":"blo..ore,v911" +10:19:47.921345 version@stat F·[2 1] S·251KiB[116KiB 134KiB] Sc·[0.50 0.00] +10:19:47.924050 db@janitor F·5 G·0 +10:19:47.924060 db@open done T·4.603129ms +=============== Jun 10, 2024 (UTC) =============== +10:20:11.776562 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:20:11.776673 version@stat F·[2 1] S·251KiB[116KiB 134KiB] Sc·[0.50 0.00] +10:20:11.776684 db@open opening +10:20:11.776713 journal@recovery F·1 +10:20:11.777346 journal@recovery recovering @19 +10:20:11.778263 version@stat F·[2 1] S·251KiB[116KiB 134KiB] Sc·[0.50 0.00] +10:20:11.780908 db@janitor F·5 G·0 +10:20:11.780918 db@open done T·4.229455ms +=============== Jun 10, 2024 (UTC) =============== +10:23:28.323489 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:23:28.323583 version@stat F·[2 1] S·251KiB[116KiB 134KiB] Sc·[0.50 0.00] +10:23:28.323595 db@open opening +10:23:28.323627 journal@recovery F·1 +10:23:28.323711 journal@recovery recovering @21 +10:23:28.324718 memdb@flush created L0@23 N·204 S·31KiB "BH:..b5f,v1617":"blo..ore,v1614" +10:23:28.324835 version@stat F·[3 1] S·283KiB[148KiB 134KiB] Sc·[0.75 0.00] +10:23:28.327449 db@janitor F·6 G·0 +10:23:28.327461 db@open done T·3.861793ms +=============== Jun 10, 2024 (UTC) =============== +10:27:08.500585 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:27:08.500746 version@stat F·[3 1] S·283KiB[148KiB 134KiB] Sc·[0.75 0.00] +10:27:08.500758 db@open opening +10:27:08.500793 journal@recovery F·1 +10:27:08.501923 journal@recovery recovering @24 +10:27:08.503154 memdb@flush created L0@26 N·246 S·39KiB "BH:..a29,v1840":"blo..ore,v1819" +10:27:08.503295 version@stat F·[4 1] S·323KiB[188KiB 134KiB] Sc·[1.00 0.00] +10:27:08.505975 db@janitor F·7 G·0 +10:27:08.505988 db@open done T·5.226534ms +10:27:08.506015 table@compaction L0·4 -> L1·1 S·323KiB Q·2060 +10:27:08.510618 table@build created L1@29 N·1711 S·322KiB "BH:..c56,v1346":"blo..ore,v2059" +10:27:08.510638 version@stat F·[0 1] S·322KiB[0B 322KiB] Sc·[0.00 0.00] +10:27:08.511249 table@compaction committed F-4 S-107B Ke·0 D·200 T·5.214384ms +10:27:08.511348 table@remove removed @23 +10:27:08.511400 table@remove removed @18 +10:27:08.511431 table@remove removed @15 +10:27:08.511480 table@remove removed @14 +=============== Jun 10, 2024 (UTC) =============== +10:30:28.116246 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:30:28.116355 version@stat F·[0 1] S·322KiB[0B 322KiB] Sc·[0.00 0.00] +10:30:28.116372 db@open opening +10:30:28.116418 journal@recovery F·1 +10:30:28.116502 journal@recovery recovering @27 +10:30:28.117379 memdb@flush created L0@30 N·120 S·19KiB "BH:..dcb,v2099":"blo..ore,v2066" +10:30:28.117497 version@stat F·[1 1] S·342KiB[19KiB 322KiB] Sc·[0.25 0.00] +10:30:28.120089 db@janitor F·5 G·1 +10:30:28.120096 db@janitor removing table-26 +10:30:28.120134 db@open done T·3.757482ms +=============== Jun 10, 2024 (UTC) =============== +11:11:44.693199 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:11:44.693313 version@stat F·[1 1] S·342KiB[19KiB 322KiB] Sc·[0.25 0.00] +11:11:44.693325 db@open opening +11:11:44.693356 journal@recovery F·1 +11:11:44.693437 journal@recovery recovering @31 +11:11:44.694528 memdb@flush created L0@33 N·300 S·46KiB "BH:..2f2,v2400":"blo..ore,v2187" +11:11:44.694644 version@stat F·[2 1] S·388KiB[65KiB 322KiB] Sc·[0.50 0.00] +11:11:44.697251 db@janitor F·5 G·0 +11:11:44.697263 db@open done T·3.934983ms +11:13:15.541536 table@compaction L0·2 -> L1·1 S·388KiB Q·2590 +11:13:15.544998 table@build created L1@36 N·2061 S·388KiB "BH:..c56,v1346":"blo..ore,v2481" +11:13:15.545019 version@stat F·[0 1] S·388KiB[0B 388KiB] Sc·[0.00 0.00] +11:13:15.545611 table@compaction committed F-2 S-832B Ke·0 D·70 T·4.050233ms +11:13:15.545675 table@remove removed @30 +11:13:15.545763 table@remove removed @29 +=============== Jun 10, 2024 (UTC) =============== +11:15:00.477161 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:15:00.477270 version@stat F·[0 1] S·388KiB[0B 388KiB] Sc·[0.00 0.00] +11:15:00.477284 db@open opening +11:15:00.477314 journal@recovery F·1 +11:15:00.477397 journal@recovery recovering @34 +11:15:00.478341 memdb@flush created L0@37 N·144 S·31KiB "BH:..2ce,v2497":"blo..ore,v2488" +11:15:00.478455 version@stat F·[1 1] S·419KiB[31KiB 388KiB] Sc·[0.25 0.00] +11:15:00.480996 db@janitor F·5 G·1 +11:15:00.481003 db@janitor removing table-33 +11:15:00.481043 db@open done T·3.754491ms +=============== Jun 10, 2024 (UTC) =============== +11:21:08.303989 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:21:08.304092 version@stat F·[1 1] S·419KiB[31KiB 388KiB] Sc·[0.25 0.00] +11:21:08.304103 db@open opening +11:21:08.304132 journal@recovery F·1 +11:21:08.304223 journal@recovery recovering @38 +11:21:08.304359 version@stat F·[1 1] S·419KiB[31KiB 388KiB] Sc·[0.25 0.00] +11:21:08.306977 db@janitor F·4 G·0 +11:21:08.306988 db@open done T·2.880913ms +=============== Jun 10, 2024 (UTC) =============== +11:22:28.244211 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:22:28.244337 version@stat F·[1 1] S·419KiB[31KiB 388KiB] Sc·[0.25 0.00] +11:22:28.244353 db@open opening +11:22:28.244391 journal@recovery F·1 +11:22:28.244497 journal@recovery recovering @40 +11:22:28.245313 memdb@flush created L0@42 N·6 S·953B "BH:..2f0,v2630":"blo..ore,v2633" +11:22:28.245443 version@stat F·[2 1] S·420KiB[31KiB 388KiB] Sc·[0.50 0.00] +11:22:28.247973 db@janitor F·5 G·0 +11:22:28.247983 db@open done T·3.62546ms +=============== Jun 10, 2024 (UTC) =============== +11:23:15.336431 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:23:15.336541 version@stat F·[2 1] S·420KiB[31KiB 388KiB] Sc·[0.50 0.00] +11:23:15.336553 db@open opening +11:23:15.336583 journal@recovery F·1 +11:23:15.336660 journal@recovery recovering @43 +11:23:15.337523 memdb@flush created L0@45 N·30 S·3KiB "BH:..cbb,v2643":"blo..ore,v2640" +11:23:15.337641 version@stat F·[3 1] S·423KiB[35KiB 388KiB] Sc·[0.75 0.00] +11:23:15.340336 db@janitor F·6 G·0 +11:23:15.340348 db@open done T·3.790991ms +=============== Jun 10, 2024 (UTC) =============== +11:29:08.705083 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:29:08.705174 version@stat F·[3 1] S·423KiB[35KiB 388KiB] Sc·[0.75 0.00] +11:29:08.705194 db@open opening +11:29:08.705235 journal@recovery F·1 +11:29:08.705313 journal@recovery recovering @46 +11:29:08.706537 memdb@flush created L0@48 N·396 S·61KiB "BH:..1b8,v2968":"blo..ore,v2671" +11:29:08.706659 version@stat F·[4 1] S·485KiB[97KiB 388KiB] Sc·[1.00 0.00] +11:29:08.709326 db@janitor F·7 G·0 +11:29:08.709338 db@open done T·4.136295ms +11:29:08.709366 table@compaction L0·4 -> L1·1 S·485KiB Q·3062 +11:29:08.713655 table@build created L1@51 N·2541 S·486KiB "BH:..c56,v1346":"blo..ore,v3061" +11:29:08.713678 version@stat F·[0 1] S·486KiB[0B 486KiB] Sc·[0.00 0.00] +11:29:08.715230 table@compaction committed F-4 S+616B Ke·0 D·96 T·5.842169ms +11:29:08.715295 table@remove removed @45 +11:29:08.715332 table@remove removed @42 +11:29:08.715447 table@remove removed @37 +11:29:08.715550 table@remove removed @36 +=============== Jun 10, 2024 (UTC) =============== +11:30:42.383930 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:42.384026 version@stat F·[0 1] S·486KiB[0B 486KiB] Sc·[0.00 0.00] +11:30:42.384039 db@open opening +11:30:42.384068 journal@recovery F·1 +11:30:42.384145 journal@recovery recovering @49 +11:30:42.385044 memdb@flush created L0@52 N·102 S·16KiB "BH:..c64,v3107":"blo..ore,v3068" +11:30:42.385164 version@stat F·[1 1] S·502KiB[16KiB 486KiB] Sc·[0.25 0.00] +11:30:42.387750 db@janitor F·5 G·1 +11:30:42.387758 db@janitor removing table-48 +11:30:42.387801 db@open done T·3.759432ms +=============== Jun 10, 2024 (UTC) =============== +11:30:55.643138 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:55.643248 version@stat F·[1 1] S·502KiB[16KiB 486KiB] Sc·[0.25 0.00] +11:30:55.643264 db@open opening +11:30:55.643296 journal@recovery F·1 +11:30:55.643389 journal@recovery recovering @53 +11:30:55.644261 memdb@flush created L0@55 N·6 S·961B "BH:..031,v3168":"blo..ore,v3171" +11:30:55.644377 version@stat F·[2 1] S·503KiB[17KiB 486KiB] Sc·[0.50 0.00] +11:30:55.647123 db@janitor F·5 G·0 +11:30:55.647137 db@open done T·3.868243ms +=============== Jun 10, 2024 (UTC) =============== +11:36:36.169004 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:36:36.169220 version@stat F·[2 1] S·503KiB[17KiB 486KiB] Sc·[0.50 0.00] +11:36:36.169245 db@open opening +11:36:36.169298 journal@recovery F·1 +11:36:36.169422 journal@recovery recovering @56 +11:36:36.170654 memdb@flush created L0@58 N·360 S·56KiB "BH:..acb,v3439":"blo..ore,v3178" +11:36:36.171816 version@stat F·[3 1] S·560KiB[74KiB 486KiB] Sc·[0.75 0.00] +11:36:36.174552 db@janitor F·6 G·0 +11:36:36.174567 db@open done T·5.315235ms +=============== Jun 10, 2024 (UTC) =============== +12:01:05.060492 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:01:05.060613 version@stat F·[3 1] S·560KiB[74KiB 486KiB] Sc·[0.75 0.00] +12:01:05.060632 db@open opening +12:01:05.060677 journal@recovery F·1 +12:01:05.060769 journal@recovery recovering @59 +12:01:05.061925 memdb@flush created L0@61 N·300 S·46KiB "BH:..fe3,v3794":"blo..ore,v3539" +12:01:05.062094 version@stat F·[4 1] S·607KiB[120KiB 486KiB] Sc·[1.00 0.00] +12:01:05.065257 db@janitor F·7 G·0 +12:01:05.065272 db@open done T·4.633571ms +12:01:05.065310 table@compaction L0·4 -> L1·1 S·607KiB Q·3834 +12:01:05.070497 table@build created L1@64 N·3181 S·605KiB "BH:..c56,v1346":"blo..ore,v3833" +12:01:05.070521 version@stat F·[0 1] S·605KiB[0B 605KiB] Sc·[0.00 0.01] +12:01:05.071073 table@compaction committed F-4 S-2KiB Ke·0 D·128 T·5.740311ms +12:01:05.071142 table@remove removed @58 +12:01:05.071177 table@remove removed @55 +12:01:05.071219 table@remove removed @52 +12:01:05.071335 table@remove removed @51 +=============== Jun 10, 2024 (UTC) =============== +12:03:00.046263 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:00.046410 version@stat F·[0 1] S·605KiB[0B 605KiB] Sc·[0.00 0.01] +12:03:00.046426 db@open opening +12:03:00.046461 journal@recovery F·1 +12:03:00.046563 journal@recovery recovering @62 +12:03:00.047387 memdb@flush created L0@65 N·18 S·2KiB "BH:..3b0,v3843":"blo..ore,v3840" +12:03:00.047524 version@stat F·[1 1] S·607KiB[2KiB 605KiB] Sc·[0.25 0.01] +12:03:00.050072 db@janitor F·5 G·1 +12:03:00.050081 db@janitor removing table-61 +12:03:00.050125 db@open done T·3.693892ms +=============== Jun 10, 2024 (UTC) =============== +12:03:19.754783 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:19.754881 version@stat F·[1 1] S·607KiB[2KiB 605KiB] Sc·[0.25 0.01] +12:03:19.754895 db@open opening +12:03:19.754926 journal@recovery F·1 +12:03:19.755014 journal@recovery recovering @66 +12:03:19.755813 memdb@flush created L0@68 N·12 S·1KiB "BH:..067,v3862":"blo..ore,v3859" +12:03:19.755990 version@stat F·[2 1] S·608KiB[3KiB 605KiB] Sc·[0.50 0.01] +12:03:19.759200 db@janitor F·5 G·0 +12:03:19.759219 db@open done T·4.318108ms +=============== Jun 10, 2024 (UTC) =============== +12:04:23.847650 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:04:23.847761 version@stat F·[2 1] S·608KiB[3KiB 605KiB] Sc·[0.50 0.01] +12:04:23.847777 db@open opening +12:04:23.847810 journal@recovery F·1 +12:04:23.848051 journal@recovery recovering @69 +12:04:23.849318 memdb@flush created L0@71 N·6 S·987B "BH:..960,v3869":"blo..ore,v3872" +12:04:23.849590 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:04:23.852801 db@janitor F·6 G·0 +12:04:23.852816 db@open done T·5.034254ms +=============== Jun 10, 2024 (UTC) =============== +12:07:46.929784 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:07:46.929882 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:07:46.929895 db@open opening +12:07:46.929928 journal@recovery F·1 +12:07:46.931774 journal@recovery recovering @72 +12:07:46.931913 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:07:46.935410 db@janitor F·6 G·0 +12:07:46.935423 db@open done T·5.523909ms +=============== Jun 10, 2024 (UTC) =============== +12:08:02.153961 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:08:02.154072 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:08:02.154087 db@open opening +12:08:02.154122 journal@recovery F·1 +12:08:02.154210 journal@recovery recovering @74 +12:08:02.154367 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:08:02.157005 db@janitor F·6 G·0 +12:08:02.157017 db@open done T·2.924626ms +=============== Jun 10, 2024 (UTC) =============== +12:27:21.480718 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:27:21.480823 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:27:21.480837 db@open opening +12:27:21.480873 journal@recovery F·1 +12:27:21.480960 journal@recovery recovering @76 +12:27:21.481104 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:27:21.485083 db@janitor F·6 G·0 +12:27:21.485095 db@open done T·4.253597ms +=============== Jun 10, 2024 (UTC) =============== +12:56:43.851141 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:56:43.851239 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:56:43.851254 db@open opening +12:56:43.851286 journal@recovery F·1 +12:56:43.851381 journal@recovery recovering @78 +12:56:43.851571 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:56:43.855100 db@janitor F·6 G·0 +12:56:43.855112 db@open done T·3.853263ms +=============== Jun 10, 2024 (UTC) =============== +12:57:38.425919 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:57:38.426025 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:57:38.426040 db@open opening +12:57:38.426072 journal@recovery F·1 +12:57:38.426161 journal@recovery recovering @80 +12:57:38.426313 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:57:38.428964 db@janitor F·6 G·0 +12:57:38.428977 db@open done T·2.931815ms +=============== Jun 10, 2024 (UTC) =============== +12:58:01.520107 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:01.520223 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:58:01.520238 db@open opening +12:58:01.520276 journal@recovery F·1 +12:58:01.520358 journal@recovery recovering @82 +12:58:01.520492 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:58:01.523157 db@janitor F·6 G·0 +12:58:01.523170 db@open done T·2.926926ms +=============== Jun 10, 2024 (UTC) =============== +12:58:49.040153 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:49.040284 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:58:49.040302 db@open opening +12:58:49.040337 journal@recovery F·1 +12:58:49.040426 journal@recovery recovering @84 +12:58:49.040585 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:58:49.043662 db@janitor F·6 G·0 +12:58:49.043680 db@open done T·3.373349ms +=============== Jun 10, 2024 (UTC) =============== +12:59:05.178695 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:05.178791 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:59:05.178805 db@open opening +12:59:05.178844 journal@recovery F·1 +12:59:05.178937 journal@recovery recovering @86 +12:59:05.179077 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:59:05.182753 db@janitor F·6 G·0 +12:59:05.182765 db@open done T·3.954894ms +=============== Jun 10, 2024 (UTC) =============== +12:59:32.457946 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:32.458049 version@stat F·[3 1] S·609KiB[4KiB 605KiB] Sc·[0.75 0.01] +12:59:32.458062 db@open opening +12:59:32.458095 journal@recovery F·1 +12:59:32.458194 journal@recovery recovering @88 +12:59:32.458994 memdb@flush created L0@90 N·6 S·969B "BH:..67e,v3876":"blo..ore,v3879" +12:59:32.459122 version@stat F·[4 1] S·610KiB[5KiB 605KiB] Sc·[1.00 0.01] +12:59:32.461936 db@janitor F·7 G·0 +12:59:32.461950 db@open done T·3.882674ms +12:59:32.461975 table@compaction L0·4 -> L1·1 S·610KiB Q·3880 +12:59:32.466693 table@build created L1@93 N·3216 S·611KiB "BH:..c56,v1346":"blo..ore,v3879" +12:59:32.466712 version@stat F·[0 1] S·611KiB[0B 611KiB] Sc·[0.00 0.01] +12:59:32.469342 table@compaction committed F-4 S+1KiB Ke·0 D·7 T·7.345334ms +12:59:32.469407 table@remove removed @71 +12:59:32.469441 table@remove removed @68 +12:59:32.469472 table@remove removed @65 +12:59:32.469610 table@remove removed @64 +=============== Jul 3, 2024 (UTC) =============== +17:52:58.389900 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:52:58.390016 version@stat F·[0 1] S·611KiB[0B 611KiB] Sc·[0.00 0.01] +17:52:58.390030 db@open opening +17:52:58.390060 journal@recovery F·1 +17:52:58.390141 journal@recovery recovering @91 +17:52:58.390270 version@stat F·[0 1] S·611KiB[0B 611KiB] Sc·[0.00 0.01] +17:52:58.392841 db@janitor F·4 G·1 +17:52:58.392849 db@janitor removing table-90 +17:52:58.392880 db@open done T·2.845504ms +=============== Jul 3, 2024 (UTC) =============== +17:59:01.756021 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:59:01.756195 version@stat F·[0 1] S·611KiB[0B 611KiB] Sc·[0.00 0.01] +17:59:01.756211 db@open opening +17:59:01.756249 journal@recovery F·1 +17:59:01.756409 journal@recovery recovering @94 +17:59:01.758513 memdb@flush created L0@96 N·414 S·72KiB "BH:..19a,v4003":"blo..ore,v3886" +17:59:01.758668 version@stat F·[1 1] S·684KiB[72KiB 611KiB] Sc·[0.25 0.01] +17:59:01.761454 db@janitor F·4 G·0 +17:59:01.761470 db@open done T·5.254513ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.909640 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.909735 version@stat F·[1 1] S·684KiB[72KiB 611KiB] Sc·[0.25 0.01] +05:10:40.909748 db@open opening +05:10:40.909778 journal@recovery F·1 +05:10:40.909866 journal@recovery recovering @97 +05:10:40.910854 memdb@flush created L0@99 N·30 S·3KiB "BH:..eed,v4304":"blo..ore,v4301" +05:10:40.911054 version@stat F·[2 1] S·688KiB[76KiB 611KiB] Sc·[0.50 0.01] +05:10:40.915558 db@janitor F·5 G·0 +05:10:40.915581 db@open done T·5.827674ms +05:11:03.981547 table@compaction L0·2 -> L1·1 S·688KiB Q·4350 +05:11:03.987164 table@build created L1@102 N·3586 S·689KiB "BH:..c56,v1346":"blo..ore,v4325" +05:11:03.987190 version@stat F·[0 1] S·689KiB[0B 689KiB] Sc·[0.00 0.01] +05:11:03.987766 table@compaction committed F-2 S+1KiB Ke·0 D·74 T·6.185418ms +05:11:03.987844 table@remove removed @96 +05:11:03.987972 table@remove removed @93 +=============== Oct 17, 2024 (UTC) =============== +06:44:58.661274 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.661360 version@stat F·[0 1] S·689KiB[0B 689KiB] Sc·[0.00 0.01] +06:44:58.661370 db@open opening +06:44:58.661399 journal@recovery F·1 +06:44:58.661518 journal@recovery recovering @100 +06:44:58.681937 memdb@flush created L0@103 N·864 S·144KiB "BH:..573,v4461":"blo..ore,v4332" +06:44:58.682057 version@stat F·[1 1] S·834KiB[144KiB 689KiB] Sc·[0.25 0.01] +06:44:58.690133 db@janitor F·5 G·1 +06:44:58.690138 db@janitor removing table-99 +06:44:58.690162 db@open done T·28.789108ms +=============== Oct 17, 2024 (UTC) =============== +06:45:46.638683 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.638799 version@stat F·[1 1] S·834KiB[144KiB 689KiB] Sc·[0.25 0.01] +06:45:46.638816 db@open opening +06:45:46.638854 journal@recovery F·1 +06:45:46.638937 journal@recovery recovering @104 +06:45:46.639741 memdb@flush created L0@106 N·42 S·10KiB "BH:..d0b,v5194":"blo..ore,v5197" +06:45:46.639858 version@stat F·[2 1] S·844KiB[154KiB 689KiB] Sc·[0.50 0.01] +06:45:46.642453 db@janitor F·5 G·0 +06:45:46.642465 db@open done T·3.643621ms +06:46:17.600570 table@compaction L0·2 -> L1·1 S·844KiB Q·5270 +06:46:17.605912 table@build created L1@109 N·4341 S·845KiB "BH:..c56,v1346":"blo..ore,v5233" +06:46:17.605934 version@stat F·[0 1] S·845KiB[0B 845KiB] Sc·[0.00 0.01] +06:46:17.607466 table@compaction committed F-2 S+552B Ke·0 D·151 T·6.876138ms +06:46:17.607551 table@remove removed @103 +06:46:17.607706 table@remove removed @102 +=============== Oct 17, 2024 (UTC) =============== +06:47:26.263473 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.263569 version@stat F·[0 1] S·845KiB[0B 845KiB] Sc·[0.00 0.01] +06:47:26.263581 db@open opening +06:47:26.263610 journal@recovery F·1 +06:47:26.263695 journal@recovery recovering @107 +06:47:26.264737 memdb@flush created L0@110 N·90 S·14KiB "BH:..5c8,v5321":"blo..ore,v5240" +06:47:26.264916 version@stat F·[1 1] S·859KiB[14KiB 845KiB] Sc·[0.25 0.01] +06:47:26.269660 db@janitor F·5 G·1 +06:47:26.269667 db@janitor removing table-106 +06:47:26.269704 db@open done T·6.119582ms diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/MANIFEST-000112 b/.docker/container-state/atom-testnet-data/data/blockstore.db/MANIFEST-000112 new file mode 100644 index 0000000000..07d83114fb Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/blockstore.db/MANIFEST-000112 differ diff --git a/.docker/container-state/atom-testnet-data/data/cs.wal/wal b/.docker/container-state/atom-testnet-data/data/cs.wal/wal new file mode 100644 index 0000000000..58106e91fa Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/cs.wal/wal differ diff --git a/.docker/container-state/atom-testnet-data/data/evidence.db/000076.log b/.docker/container-state/atom-testnet-data/data/evidence.db/000076.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/atom-testnet-data/data/evidence.db/CURRENT b/.docker/container-state/atom-testnet-data/data/evidence.db/CURRENT new file mode 100644 index 0000000000..c7a124bfc3 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/evidence.db/CURRENT @@ -0,0 +1 @@ +MANIFEST-000077 diff --git a/.docker/container-state/atom-testnet-data/data/evidence.db/CURRENT.bak b/.docker/container-state/atom-testnet-data/data/evidence.db/CURRENT.bak new file mode 100644 index 0000000000..d2ea14ced0 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/evidence.db/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000075 diff --git a/.docker/container-state/atom-testnet-data/data/evidence.db/LOCK b/.docker/container-state/atom-testnet-data/data/evidence.db/LOCK new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/atom-testnet-data/data/evidence.db/LOG b/.docker/container-state/atom-testnet-data/data/evidence.db/LOG new file mode 100644 index 0000000000..e4d4d0631a --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/evidence.db/LOG @@ -0,0 +1,348 @@ +=============== Jun 3, 2024 (+03) =============== +14:44:32.509654 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +14:44:32.511481 db@open opening +14:44:32.511607 version@stat F·[] S·0B[] Sc·[] +14:44:32.512206 db@janitor F·2 G·0 +14:44:32.512222 db@open done T·734.856µs +=============== Jun 6, 2024 (+03) =============== +18:04:49.080272 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:04:49.080340 version@stat F·[] S·0B[] Sc·[] +18:04:49.080350 db@open opening +18:04:49.080377 journal@recovery F·1 +18:04:49.080469 journal@recovery recovering @1 +18:04:49.080588 version@stat F·[] S·0B[] Sc·[] +18:04:49.083092 db@janitor F·2 G·0 +18:04:49.083102 db@open done T·2.748954ms +=============== Jun 6, 2024 (+03) =============== +18:09:26.436024 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:09:26.436087 version@stat F·[] S·0B[] Sc·[] +18:09:26.436097 db@open opening +18:09:26.436125 journal@recovery F·1 +18:09:26.436219 journal@recovery recovering @2 +18:09:26.436339 version@stat F·[] S·0B[] Sc·[] +18:09:26.440379 db@janitor F·2 G·0 +18:09:26.440390 db@open done T·4.288777ms +=============== Jun 6, 2024 (+03) =============== +19:18:28.712802 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +19:18:28.712862 version@stat F·[] S·0B[] Sc·[] +19:18:28.712871 db@open opening +19:18:28.712895 journal@recovery F·1 +19:18:28.712989 journal@recovery recovering @4 +19:18:28.713116 version@stat F·[] S·0B[] Sc·[] +19:18:28.715549 db@janitor F·2 G·0 +19:18:28.715559 db@open done T·2.685744ms +=============== Jun 6, 2024 (+03) =============== +19:29:57.954497 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +19:29:57.954568 version@stat F·[] S·0B[] Sc·[] +19:29:57.954580 db@open opening +19:29:57.954621 journal@recovery F·1 +19:29:57.954732 journal@recovery recovering @6 +19:29:57.954867 version@stat F·[] S·0B[] Sc·[] +19:29:57.958415 db@janitor F·2 G·0 +19:29:57.958429 db@open done T·3.844423ms +=============== Jun 10, 2024 (UTC) =============== +10:09:48.953791 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:09:48.953853 version@stat F·[] S·0B[] Sc·[] +10:09:48.953863 db@open opening +10:09:48.953887 journal@recovery F·1 +10:09:48.953967 journal@recovery recovering @8 +10:09:48.954082 version@stat F·[] S·0B[] Sc·[] +10:09:48.956576 db@janitor F·2 G·0 +10:09:48.956585 db@open done T·2.718252ms +=============== Jun 10, 2024 (UTC) =============== +10:19:47.937791 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:19:47.937852 version@stat F·[] S·0B[] Sc·[] +10:19:47.937862 db@open opening +10:19:47.937889 journal@recovery F·1 +10:19:47.937963 journal@recovery recovering @10 +10:19:47.938077 version@stat F·[] S·0B[] Sc·[] +10:19:47.940744 db@janitor F·2 G·0 +10:19:47.940754 db@open done T·2.889024ms +=============== Jun 10, 2024 (UTC) =============== +10:20:11.788441 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:20:11.788536 version@stat F·[] S·0B[] Sc·[] +10:20:11.788548 db@open opening +10:20:11.788589 journal@recovery F·1 +10:20:11.788670 journal@recovery recovering @12 +10:20:11.788927 version@stat F·[] S·0B[] Sc·[] +10:20:11.791609 db@janitor F·2 G·0 +10:20:11.791619 db@open done T·3.067276ms +=============== Jun 10, 2024 (UTC) =============== +10:23:28.338513 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:23:28.338581 version@stat F·[] S·0B[] Sc·[] +10:23:28.338593 db@open opening +10:23:28.338623 journal@recovery F·1 +10:23:28.338701 journal@recovery recovering @14 +10:23:28.338821 version@stat F·[] S·0B[] Sc·[] +10:23:28.341447 db@janitor F·2 G·0 +10:23:28.341457 db@open done T·2.859444ms +=============== Jun 10, 2024 (UTC) =============== +10:27:08.517200 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:27:08.517262 version@stat F·[] S·0B[] Sc·[] +10:27:08.517272 db@open opening +10:27:08.517297 journal@recovery F·1 +10:27:08.517572 journal@recovery recovering @16 +10:27:08.520411 version@stat F·[] S·0B[] Sc·[] +10:27:08.524138 db@janitor F·2 G·0 +10:27:08.524148 db@open done T·6.872488ms +=============== Jun 10, 2024 (UTC) =============== +10:30:28.131018 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:30:28.131081 version@stat F·[] S·0B[] Sc·[] +10:30:28.131091 db@open opening +10:30:28.131116 journal@recovery F·1 +10:30:28.132992 journal@recovery recovering @18 +10:30:28.133111 version@stat F·[] S·0B[] Sc·[] +10:30:28.135680 db@janitor F·2 G·0 +10:30:28.135692 db@open done T·4.597388ms +=============== Jun 10, 2024 (UTC) =============== +11:11:44.709294 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:11:44.709384 version@stat F·[] S·0B[] Sc·[] +11:11:44.709399 db@open opening +11:11:44.709429 journal@recovery F·1 +11:11:44.709514 journal@recovery recovering @20 +11:11:44.709646 version@stat F·[] S·0B[] Sc·[] +11:11:44.712300 db@janitor F·2 G·0 +11:11:44.712313 db@open done T·2.909154ms +=============== Jun 10, 2024 (UTC) =============== +11:15:00.492747 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:15:00.492807 version@stat F·[] S·0B[] Sc·[] +11:15:00.492817 db@open opening +11:15:00.492844 journal@recovery F·1 +11:15:00.494716 journal@recovery recovering @22 +11:15:00.494843 version@stat F·[] S·0B[] Sc·[] +11:15:00.497465 db@janitor F·2 G·0 +11:15:00.497477 db@open done T·4.656458ms +=============== Jun 10, 2024 (UTC) =============== +11:21:08.314888 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:21:08.314951 version@stat F·[] S·0B[] Sc·[] +11:21:08.314961 db@open opening +11:21:08.314989 journal@recovery F·1 +11:21:08.316843 journal@recovery recovering @24 +11:21:08.318743 version@stat F·[] S·0B[] Sc·[] +11:21:08.321282 db@janitor F·2 G·0 +11:21:08.321291 db@open done T·6.326252ms +=============== Jun 10, 2024 (UTC) =============== +11:22:28.256835 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:22:28.256941 version@stat F·[] S·0B[] Sc·[] +11:22:28.256955 db@open opening +11:22:28.256996 journal@recovery F·1 +11:22:28.259268 journal@recovery recovering @26 +11:22:28.261218 version@stat F·[] S·0B[] Sc·[] +11:22:28.265181 db@janitor F·2 G·0 +11:22:28.265201 db@open done T·8.241029ms +=============== Jun 10, 2024 (UTC) =============== +11:23:15.352058 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:23:15.352127 version@stat F·[] S·0B[] Sc·[] +11:23:15.352138 db@open opening +11:23:15.352170 journal@recovery F·1 +11:23:15.353995 journal@recovery recovering @28 +11:23:15.354128 version@stat F·[] S·0B[] Sc·[] +11:23:15.356631 db@janitor F·2 G·0 +11:23:15.356643 db@open done T·4.500648ms +=============== Jun 10, 2024 (UTC) =============== +11:29:08.722698 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:29:08.722765 version@stat F·[] S·0B[] Sc·[] +11:29:08.722776 db@open opening +11:29:08.722805 journal@recovery F·1 +11:29:08.722880 journal@recovery recovering @30 +11:29:08.723005 version@stat F·[] S·0B[] Sc·[] +11:29:08.725629 db@janitor F·2 G·0 +11:29:08.725646 db@open done T·2.865544ms +=============== Jun 10, 2024 (UTC) =============== +11:30:42.405075 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:42.405179 version@stat F·[] S·0B[] Sc·[] +11:30:42.405200 db@open opening +11:30:42.405235 journal@recovery F·1 +11:30:42.405312 journal@recovery recovering @32 +11:30:42.405442 version@stat F·[] S·0B[] Sc·[] +11:30:42.408071 db@janitor F·2 G·0 +11:30:42.408085 db@open done T·2.879154ms +=============== Jun 10, 2024 (UTC) =============== +11:30:55.658003 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:55.658072 version@stat F·[] S·0B[] Sc·[] +11:30:55.658084 db@open opening +11:30:55.658111 journal@recovery F·1 +11:30:55.658199 journal@recovery recovering @34 +11:30:55.658334 version@stat F·[] S·0B[] Sc·[] +11:30:55.662651 db@janitor F·2 G·0 +11:30:55.662663 db@open done T·4.574568ms +=============== Jun 10, 2024 (UTC) =============== +11:36:36.186291 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:36:36.186359 version@stat F·[] S·0B[] Sc·[] +11:36:36.186371 db@open opening +11:36:36.186398 journal@recovery F·1 +11:36:36.186474 journal@recovery recovering @36 +11:36:36.186679 version@stat F·[] S·0B[] Sc·[] +11:36:36.189757 db@janitor F·2 G·0 +11:36:36.189773 db@open done T·3.397518ms +=============== Jun 10, 2024 (UTC) =============== +12:01:05.075088 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:01:05.075156 version@stat F·[] S·0B[] Sc·[] +12:01:05.075168 db@open opening +12:01:05.075206 journal@recovery F·1 +12:01:05.075344 journal@recovery recovering @38 +12:01:05.076359 version@stat F·[] S·0B[] Sc·[] +12:01:05.078935 db@janitor F·2 G·0 +12:01:05.078946 db@open done T·3.773483ms +=============== Jun 10, 2024 (UTC) =============== +12:03:00.061063 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:00.061170 version@stat F·[] S·0B[] Sc·[] +12:03:00.061191 db@open opening +12:03:00.061236 journal@recovery F·1 +12:03:00.063367 journal@recovery recovering @40 +12:03:00.063504 version@stat F·[] S·0B[] Sc·[] +12:03:00.066048 db@janitor F·2 G·0 +12:03:00.066060 db@open done T·4.861773ms +=============== Jun 10, 2024 (UTC) =============== +12:03:19.769218 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:19.769288 version@stat F·[] S·0B[] Sc·[] +12:03:19.769300 db@open opening +12:03:19.769329 journal@recovery F·1 +12:03:19.769683 journal@recovery recovering @42 +12:03:19.769952 version@stat F·[] S·0B[] Sc·[] +12:03:19.773238 db@janitor F·2 G·0 +12:03:19.773252 db@open done T·3.947185ms +=============== Jun 10, 2024 (UTC) =============== +12:04:23.863959 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:04:23.864027 version@stat F·[] S·0B[] Sc·[] +12:04:23.864040 db@open opening +12:04:23.864070 journal@recovery F·1 +12:04:23.865852 journal@recovery recovering @44 +12:04:23.867851 version@stat F·[] S·0B[] Sc·[] +12:04:23.870459 db@janitor F·2 G·0 +12:04:23.870470 db@open done T·6.425726ms +=============== Jun 10, 2024 (UTC) =============== +12:07:46.944645 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:07:46.944731 version@stat F·[] S·0B[] Sc·[] +12:07:46.944746 db@open opening +12:07:46.944789 journal@recovery F·1 +12:07:46.944888 journal@recovery recovering @46 +12:07:46.945043 version@stat F·[] S·0B[] Sc·[] +12:07:46.947664 db@janitor F·2 G·0 +12:07:46.947679 db@open done T·2.927556ms +=============== Jun 10, 2024 (UTC) =============== +12:08:02.165399 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:08:02.165472 version@stat F·[] S·0B[] Sc·[] +12:08:02.165483 db@open opening +12:08:02.165513 journal@recovery F·1 +12:08:02.167359 journal@recovery recovering @48 +12:08:02.169235 version@stat F·[] S·0B[] Sc·[] +12:08:02.171915 db@janitor F·2 G·0 +12:08:02.171939 db@open done T·6.450667ms +=============== Jun 10, 2024 (UTC) =============== +12:27:21.495936 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:27:21.496008 version@stat F·[] S·0B[] Sc·[] +12:27:21.496022 db@open opening +12:27:21.496051 journal@recovery F·1 +12:27:21.497870 journal@recovery recovering @50 +12:27:21.498007 version@stat F·[] S·0B[] Sc·[] +12:27:21.500619 db@janitor F·2 G·0 +12:27:21.500632 db@open done T·4.604ms +=============== Jun 10, 2024 (UTC) =============== +12:56:43.864647 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:56:43.864707 version@stat F·[] S·0B[] Sc·[] +12:56:43.864719 db@open opening +12:56:43.864747 journal@recovery F·1 +12:56:43.864972 journal@recovery recovering @52 +12:56:43.865922 version@stat F·[] S·0B[] Sc·[] +12:56:43.869073 db@janitor F·2 G·0 +12:56:43.869083 db@open done T·4.360008ms +=============== Jun 10, 2024 (UTC) =============== +12:57:38.438744 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:57:38.438806 version@stat F·[] S·0B[] Sc·[] +12:57:38.438818 db@open opening +12:57:38.438848 journal@recovery F·1 +12:57:38.438932 journal@recovery recovering @54 +12:57:38.439056 version@stat F·[] S·0B[] Sc·[] +12:57:38.441571 db@janitor F·2 G·0 +12:57:38.441582 db@open done T·2.760494ms +=============== Jun 10, 2024 (UTC) =============== +12:58:01.531422 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:01.531489 version@stat F·[] S·0B[] Sc·[] +12:58:01.531501 db@open opening +12:58:01.531529 journal@recovery F·1 +12:58:01.533438 journal@recovery recovering @56 +12:58:01.535416 version@stat F·[] S·0B[] Sc·[] +12:58:01.537931 db@janitor F·2 G·0 +12:58:01.537943 db@open done T·6.438126ms +=============== Jun 10, 2024 (UTC) =============== +12:58:49.052411 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:49.052487 version@stat F·[] S·0B[] Sc·[] +12:58:49.052500 db@open opening +12:58:49.052529 journal@recovery F·1 +12:58:49.054491 journal@recovery recovering @58 +12:58:49.056525 version@stat F·[] S·0B[] Sc·[] +12:58:49.059108 db@janitor F·2 G·0 +12:58:49.059123 db@open done T·6.619018ms +=============== Jun 10, 2024 (UTC) =============== +12:59:05.193570 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:05.193641 version@stat F·[] S·0B[] Sc·[] +12:59:05.193655 db@open opening +12:59:05.193683 journal@recovery F·1 +12:59:05.195651 journal@recovery recovering @60 +12:59:05.197669 version@stat F·[] S·0B[] Sc·[] +12:59:05.200219 db@janitor F·2 G·0 +12:59:05.200236 db@open done T·6.576927ms +=============== Jun 10, 2024 (UTC) =============== +12:59:32.475710 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:32.475804 version@stat F·[] S·0B[] Sc·[] +12:59:32.475819 db@open opening +12:59:32.475846 journal@recovery F·1 +12:59:32.478047 journal@recovery recovering @62 +12:59:32.478227 version@stat F·[] S·0B[] Sc·[] +12:59:32.481158 db@janitor F·2 G·0 +12:59:32.481169 db@open done T·5.346007ms +=============== Jul 3, 2024 (UTC) =============== +17:52:58.403602 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:52:58.403672 version@stat F·[] S·0B[] Sc·[] +17:52:58.403689 db@open opening +17:52:58.403719 journal@recovery F·1 +17:52:58.405639 journal@recovery recovering @64 +17:52:58.405780 version@stat F·[] S·0B[] Sc·[] +17:52:58.408336 db@janitor F·2 G·0 +17:52:58.408348 db@open done T·4.655669ms +=============== Jul 3, 2024 (UTC) =============== +17:59:01.773333 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:59:01.773412 version@stat F·[] S·0B[] Sc·[] +17:59:01.773427 db@open opening +17:59:01.773458 journal@recovery F·1 +17:59:01.773619 journal@recovery recovering @66 +17:59:01.774745 version@stat F·[] S·0B[] Sc·[] +17:59:01.777793 db@janitor F·2 G·0 +17:59:01.777805 db@open done T·4.373546ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.928961 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.929028 version@stat F·[] S·0B[] Sc·[] +05:10:40.929037 db@open opening +05:10:40.929066 journal@recovery F·1 +05:10:40.929428 journal@recovery recovering @68 +05:10:40.931225 version@stat F·[] S·0B[] Sc·[] +05:10:40.934658 db@janitor F·2 G·0 +05:10:40.934670 db@open done T·5.628153ms +=============== Oct 17, 2024 (UTC) =============== +06:44:58.730742 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.730819 version@stat F·[] S·0B[] Sc·[] +06:44:58.730838 db@open opening +06:44:58.730866 journal@recovery F·1 +06:44:58.732739 journal@recovery recovering @70 +06:44:58.734542 version@stat F·[] S·0B[] Sc·[] +06:44:58.749764 db@janitor F·2 G·0 +06:44:58.749779 db@open done T·18.938744ms +=============== Oct 17, 2024 (UTC) =============== +06:45:46.656897 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.656961 version@stat F·[] S·0B[] Sc·[] +06:45:46.656972 db@open opening +06:45:46.656996 journal@recovery F·1 +06:45:46.657075 journal@recovery recovering @72 +06:45:46.657200 version@stat F·[] S·0B[] Sc·[] +06:45:46.659741 db@janitor F·2 G·0 +06:45:46.659750 db@open done T·2.774794ms +=============== Oct 17, 2024 (UTC) =============== +06:47:26.282862 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.282927 version@stat F·[] S·0B[] Sc·[] +06:47:26.282937 db@open opening +06:47:26.282964 journal@recovery F·1 +06:47:26.283046 journal@recovery recovering @74 +06:47:26.283185 version@stat F·[] S·0B[] Sc·[] +06:47:26.285765 db@janitor F·2 G·0 +06:47:26.285779 db@open done T·2.837874ms diff --git a/.docker/container-state/atom-testnet-data/data/evidence.db/MANIFEST-000077 b/.docker/container-state/atom-testnet-data/data/evidence.db/MANIFEST-000077 new file mode 100644 index 0000000000..dd7fb6cd3d Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/evidence.db/MANIFEST-000077 differ diff --git a/.docker/container-state/atom-testnet-data/data/priv_validator_state.json b/.docker/container-state/atom-testnet-data/data/priv_validator_state.json new file mode 100644 index 0000000000..56899e7a9a --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/priv_validator_state.json @@ -0,0 +1,7 @@ +{ + "height": "888", + "round": 0, + "step": 3, + "signature": "XskRDe70STOW5qxh60WQ2GR/2x2sDaGqPnAsTb7icGjekGky1w7kO/b3nU+XzmQVDaQcYHjb8W1JRO4ue9TeAA==", + "signbytes": "76080211780300000000000022480A20F5676C89CEA03B617C53FCD05015F465FB2C14107310553F186D423A022E05E2122408011220AA62CEBCC34EFE61C30D5917935B2224E39D5019F92CE313CB8514F584F6D54E2A0C0897E6C2B80610EBD7E698013211636F736D6F736875622D746573746E6574" +} \ No newline at end of file diff --git a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/000076.log b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/000076.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/CURRENT b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/CURRENT new file mode 100644 index 0000000000..c7a124bfc3 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/CURRENT @@ -0,0 +1 @@ +MANIFEST-000077 diff --git a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/CURRENT.bak b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/CURRENT.bak new file mode 100644 index 0000000000..d2ea14ced0 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000075 diff --git a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/LOCK b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/LOCK new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/LOG b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/LOG new file mode 100644 index 0000000000..108e3e10d4 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/LOG @@ -0,0 +1,348 @@ +=============== Jun 3, 2024 (+03) =============== +14:44:32.475411 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +14:44:32.477260 db@open opening +14:44:32.477695 version@stat F·[] S·0B[] Sc·[] +14:44:32.478344 db@janitor F·2 G·0 +14:44:32.478355 db@open done T·1.087999ms +=============== Jun 6, 2024 (+03) =============== +18:04:49.043230 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:04:49.043300 version@stat F·[] S·0B[] Sc·[] +18:04:49.043310 db@open opening +18:04:49.043338 journal@recovery F·1 +18:04:49.043415 journal@recovery recovering @1 +18:04:49.043536 version@stat F·[] S·0B[] Sc·[] +18:04:49.046065 db@janitor F·2 G·0 +18:04:49.046074 db@open done T·2.760574ms +=============== Jun 6, 2024 (+03) =============== +18:09:26.397237 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:09:26.397298 version@stat F·[] S·0B[] Sc·[] +18:09:26.397308 db@open opening +18:09:26.397338 journal@recovery F·1 +18:09:26.397413 journal@recovery recovering @2 +18:09:26.397529 version@stat F·[] S·0B[] Sc·[] +18:09:26.400092 db@janitor F·2 G·0 +18:09:26.400102 db@open done T·2.789214ms +=============== Jun 6, 2024 (+03) =============== +19:18:28.680139 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +19:18:28.680197 version@stat F·[] S·0B[] Sc·[] +19:18:28.680205 db@open opening +19:18:28.680229 journal@recovery F·1 +19:18:28.680307 journal@recovery recovering @4 +19:18:28.680423 version@stat F·[] S·0B[] Sc·[] +19:18:28.682861 db@janitor F·2 G·0 +19:18:28.682869 db@open done T·2.661124ms +=============== Jun 6, 2024 (+03) =============== +19:29:57.920102 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +19:29:57.920159 version@stat F·[] S·0B[] Sc·[] +19:29:57.920167 db@open opening +19:29:57.920190 journal@recovery F·1 +19:29:57.922094 journal@recovery recovering @6 +19:29:57.922230 version@stat F·[] S·0B[] Sc·[] +19:29:57.924641 db@janitor F·2 G·0 +19:29:57.924650 db@open done T·4.480109ms +=============== Jun 10, 2024 (UTC) =============== +10:09:48.896199 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:09:48.896267 version@stat F·[] S·0B[] Sc·[] +10:09:48.896277 db@open opening +10:09:48.896314 journal@recovery F·1 +10:09:48.896391 journal@recovery recovering @8 +10:09:48.896519 version@stat F·[] S·0B[] Sc·[] +10:09:48.899646 db@janitor F·2 G·0 +10:09:48.899653 db@open done T·3.372298ms +=============== Jun 10, 2024 (UTC) =============== +10:19:47.888266 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:19:47.888339 version@stat F·[] S·0B[] Sc·[] +10:19:47.888350 db@open opening +10:19:47.888377 journal@recovery F·1 +10:19:47.888455 journal@recovery recovering @10 +10:19:47.888724 version@stat F·[] S·0B[] Sc·[] +10:19:47.891797 db@janitor F·2 G·0 +10:19:47.891807 db@open done T·3.453589ms +=============== Jun 10, 2024 (UTC) =============== +10:20:11.744814 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:20:11.744888 version@stat F·[] S·0B[] Sc·[] +10:20:11.744899 db@open opening +10:20:11.744930 journal@recovery F·1 +10:20:11.746835 journal@recovery recovering @12 +10:20:11.746968 version@stat F·[] S·0B[] Sc·[] +10:20:11.749659 db@janitor F·2 G·0 +10:20:11.749670 db@open done T·4.76632ms +=============== Jun 10, 2024 (UTC) =============== +10:23:28.290887 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:23:28.290956 version@stat F·[] S·0B[] Sc·[] +10:23:28.290967 db@open opening +10:23:28.290992 journal@recovery F·1 +10:23:28.291072 journal@recovery recovering @14 +10:23:28.291223 version@stat F·[] S·0B[] Sc·[] +10:23:28.293997 db@janitor F·2 G·0 +10:23:28.294009 db@open done T·3.037896ms +=============== Jun 10, 2024 (UTC) =============== +10:27:08.467062 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:27:08.467128 version@stat F·[] S·0B[] Sc·[] +10:27:08.467139 db@open opening +10:27:08.467165 journal@recovery F·1 +10:27:08.467379 journal@recovery recovering @16 +10:27:08.469369 version@stat F·[] S·0B[] Sc·[] +10:27:08.472017 db@janitor F·2 G·0 +10:27:08.472029 db@open done T·4.885871ms +=============== Jun 10, 2024 (UTC) =============== +10:30:28.082344 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:30:28.082423 version@stat F·[] S·0B[] Sc·[] +10:30:28.082434 db@open opening +10:30:28.082460 journal@recovery F·1 +10:30:28.084262 journal@recovery recovering @18 +10:30:28.084397 version@stat F·[] S·0B[] Sc·[] +10:30:28.086961 db@janitor F·2 G·0 +10:30:28.086969 db@open done T·4.531538ms +=============== Jun 10, 2024 (UTC) =============== +11:11:44.658330 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:11:44.658400 version@stat F·[] S·0B[] Sc·[] +11:11:44.658414 db@open opening +11:11:44.658441 journal@recovery F·1 +11:11:44.658519 journal@recovery recovering @20 +11:11:44.658644 version@stat F·[] S·0B[] Sc·[] +11:11:44.661228 db@janitor F·2 G·0 +11:11:44.661238 db@open done T·2.820283ms +=============== Jun 10, 2024 (UTC) =============== +11:15:00.439924 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:15:00.439987 version@stat F·[] S·0B[] Sc·[] +11:15:00.440001 db@open opening +11:15:00.440029 journal@recovery F·1 +11:15:00.441685 journal@recovery recovering @22 +11:15:00.443588 version@stat F·[] S·0B[] Sc·[] +11:15:00.446135 db@janitor F·2 G·0 +11:15:00.446146 db@open done T·6.140941ms +=============== Jun 10, 2024 (UTC) =============== +11:21:08.268198 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:21:08.268266 version@stat F·[] S·0B[] Sc·[] +11:21:08.268277 db@open opening +11:21:08.268302 journal@recovery F·1 +11:21:08.270129 journal@recovery recovering @24 +11:21:08.270268 version@stat F·[] S·0B[] Sc·[] +11:21:08.272787 db@janitor F·2 G·0 +11:21:08.272796 db@open done T·4.515027ms +=============== Jun 10, 2024 (UTC) =============== +11:22:28.205329 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:22:28.205394 version@stat F·[] S·0B[] Sc·[] +11:22:28.205403 db@open opening +11:22:28.205430 journal@recovery F·1 +11:22:28.207319 journal@recovery recovering @26 +11:22:28.207454 version@stat F·[] S·0B[] Sc·[] +11:22:28.210837 db@janitor F·2 G·0 +11:22:28.210847 db@open done T·5.440115ms +=============== Jun 10, 2024 (UTC) =============== +11:23:15.300491 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:23:15.300554 version@stat F·[] S·0B[] Sc·[] +11:23:15.300568 db@open opening +11:23:15.300594 journal@recovery F·1 +11:23:15.302393 journal@recovery recovering @28 +11:23:15.302518 version@stat F·[] S·0B[] Sc·[] +11:23:15.305063 db@janitor F·2 G·0 +11:23:15.305073 db@open done T·4.501347ms +=============== Jun 10, 2024 (UTC) =============== +11:29:08.669833 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:29:08.669907 version@stat F·[] S·0B[] Sc·[] +11:29:08.669919 db@open opening +11:29:08.669948 journal@recovery F·1 +11:29:08.670026 journal@recovery recovering @30 +11:29:08.670336 version@stat F·[] S·0B[] Sc·[] +11:29:08.673082 db@janitor F·2 G·0 +11:29:08.673099 db@open done T·3.175877ms +=============== Jun 10, 2024 (UTC) =============== +11:30:42.347315 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:42.347389 version@stat F·[] S·0B[] Sc·[] +11:30:42.347404 db@open opening +11:30:42.347434 journal@recovery F·1 +11:30:42.347509 journal@recovery recovering @32 +11:30:42.347634 version@stat F·[] S·0B[] Sc·[] +11:30:42.350309 db@janitor F·2 G·0 +11:30:42.350319 db@open done T·2.910614ms +=============== Jun 10, 2024 (UTC) =============== +11:30:55.606170 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:55.606265 version@stat F·[] S·0B[] Sc·[] +11:30:55.606281 db@open opening +11:30:55.606319 journal@recovery F·1 +11:30:55.606421 journal@recovery recovering @34 +11:30:55.606576 version@stat F·[] S·0B[] Sc·[] +11:30:55.609590 db@janitor F·2 G·0 +11:30:55.609602 db@open done T·3.316178ms +=============== Jun 10, 2024 (UTC) =============== +11:36:36.132677 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:36:36.132767 version@stat F·[] S·0B[] Sc·[] +11:36:36.132781 db@open opening +11:36:36.132811 journal@recovery F·1 +11:36:36.132895 journal@recovery recovering @36 +11:36:36.133060 version@stat F·[] S·0B[] Sc·[] +11:36:36.135669 db@janitor F·2 G·0 +11:36:36.135678 db@open done T·2.891694ms +=============== Jun 10, 2024 (UTC) =============== +12:01:05.022802 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:01:05.022873 version@stat F·[] S·0B[] Sc·[] +12:01:05.022887 db@open opening +12:01:05.022914 journal@recovery F·1 +12:01:05.022991 journal@recovery recovering @38 +12:01:05.023111 version@stat F·[] S·0B[] Sc·[] +12:01:05.025774 db@janitor F·2 G·0 +12:01:05.025784 db@open done T·2.892525ms +=============== Jun 10, 2024 (UTC) =============== +12:03:00.007287 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:00.007362 version@stat F·[] S·0B[] Sc·[] +12:03:00.007374 db@open opening +12:03:00.007403 journal@recovery F·1 +12:03:00.009173 journal@recovery recovering @40 +12:03:00.009326 version@stat F·[] S·0B[] Sc·[] +12:03:00.011929 db@janitor F·2 G·0 +12:03:00.011941 db@open done T·4.56198ms +=============== Jun 10, 2024 (UTC) =============== +12:03:19.717291 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:19.717361 version@stat F·[] S·0B[] Sc·[] +12:03:19.717373 db@open opening +12:03:19.717401 journal@recovery F·1 +12:03:19.717484 journal@recovery recovering @42 +12:03:19.717611 version@stat F·[] S·0B[] Sc·[] +12:03:19.720112 db@janitor F·2 G·0 +12:03:19.720123 db@open done T·2.746074ms +=============== Jun 10, 2024 (UTC) =============== +12:04:23.809510 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:04:23.809653 version@stat F·[] S·0B[] Sc·[] +12:04:23.809666 db@open opening +12:04:23.809697 journal@recovery F·1 +12:04:23.809785 journal@recovery recovering @44 +12:04:23.809927 version@stat F·[] S·0B[] Sc·[] +12:04:23.812577 db@janitor F·2 G·0 +12:04:23.812591 db@open done T·2.919426ms +=============== Jun 10, 2024 (UTC) =============== +12:07:46.892377 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:07:46.892460 version@stat F·[] S·0B[] Sc·[] +12:07:46.892473 db@open opening +12:07:46.892501 journal@recovery F·1 +12:07:46.892581 journal@recovery recovering @46 +12:07:46.892712 version@stat F·[] S·0B[] Sc·[] +12:07:46.895287 db@janitor F·2 G·0 +12:07:46.895300 db@open done T·2.822145ms +=============== Jun 10, 2024 (UTC) =============== +12:08:02.113224 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:08:02.113300 version@stat F·[] S·0B[] Sc·[] +12:08:02.113310 db@open opening +12:08:02.113335 journal@recovery F·1 +12:08:02.115124 journal@recovery recovering @48 +12:08:02.115264 version@stat F·[] S·0B[] Sc·[] +12:08:02.117935 db@janitor F·2 G·0 +12:08:02.117944 db@open done T·4.630711ms +=============== Jun 10, 2024 (UTC) =============== +12:27:21.439818 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:27:21.439895 version@stat F·[] S·0B[] Sc·[] +12:27:21.439908 db@open opening +12:27:21.439934 journal@recovery F·1 +12:27:21.441724 journal@recovery recovering @50 +12:27:21.441856 version@stat F·[] S·0B[] Sc·[] +12:27:21.444635 db@janitor F·2 G·0 +12:27:21.444647 db@open done T·4.734541ms +=============== Jun 10, 2024 (UTC) =============== +12:56:43.813189 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:56:43.813261 version@stat F·[] S·0B[] Sc·[] +12:56:43.813274 db@open opening +12:56:43.813301 journal@recovery F·1 +12:56:43.813390 journal@recovery recovering @52 +12:56:43.813515 version@stat F·[] S·0B[] Sc·[] +12:56:43.816070 db@janitor F·2 G·0 +12:56:43.816079 db@open done T·2.800785ms +=============== Jun 10, 2024 (UTC) =============== +12:57:38.388069 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:57:38.388136 version@stat F·[] S·0B[] Sc·[] +12:57:38.388149 db@open opening +12:57:38.388177 journal@recovery F·1 +12:57:38.388277 journal@recovery recovering @54 +12:57:38.388405 version@stat F·[] S·0B[] Sc·[] +12:57:38.390999 db@janitor F·2 G·0 +12:57:38.391009 db@open done T·2.855815ms +=============== Jun 10, 2024 (UTC) =============== +12:58:01.477760 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:01.477835 version@stat F·[] S·0B[] Sc·[] +12:58:01.477847 db@open opening +12:58:01.477875 journal@recovery F·1 +12:58:01.479806 journal@recovery recovering @56 +12:58:01.481861 version@stat F·[] S·0B[] Sc·[] +12:58:01.485382 db@janitor F·2 G·0 +12:58:01.485393 db@open done T·7.540906ms +=============== Jun 10, 2024 (UTC) =============== +12:58:49.000209 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:49.000280 version@stat F·[] S·0B[] Sc·[] +12:58:49.000293 db@open opening +12:58:49.000320 journal@recovery F·1 +12:58:49.002268 journal@recovery recovering @58 +12:58:49.002409 version@stat F·[] S·0B[] Sc·[] +12:58:49.004946 db@janitor F·2 G·0 +12:58:49.004957 db@open done T·4.658861ms +=============== Jun 10, 2024 (UTC) =============== +12:59:05.137398 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:05.137461 version@stat F·[] S·0B[] Sc·[] +12:59:05.137473 db@open opening +12:59:05.137500 journal@recovery F·1 +12:59:05.139458 journal@recovery recovering @60 +12:59:05.139596 version@stat F·[] S·0B[] Sc·[] +12:59:05.142252 db@janitor F·2 G·0 +12:59:05.142263 db@open done T·4.785572ms +=============== Jun 10, 2024 (UTC) =============== +12:59:32.415003 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:32.415075 version@stat F·[] S·0B[] Sc·[] +12:59:32.415092 db@open opening +12:59:32.415119 journal@recovery F·1 +12:59:32.417105 journal@recovery recovering @62 +12:59:32.417251 version@stat F·[] S·0B[] Sc·[] +12:59:32.420493 db@janitor F·2 G·0 +12:59:32.420505 db@open done T·5.407497ms +=============== Jul 3, 2024 (UTC) =============== +17:52:58.348722 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:52:58.348793 version@stat F·[] S·0B[] Sc·[] +17:52:58.348811 db@open opening +17:52:58.348841 journal@recovery F·1 +17:52:58.350768 journal@recovery recovering @64 +17:52:58.350929 version@stat F·[] S·0B[] Sc·[] +17:52:58.355593 db@janitor F·2 G·0 +17:52:58.355602 db@open done T·6.787586ms +=============== Jul 3, 2024 (UTC) =============== +17:59:01.716570 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:59:01.716652 version@stat F·[] S·0B[] Sc·[] +17:59:01.716664 db@open opening +17:59:01.716693 journal@recovery F·1 +17:59:01.716779 journal@recovery recovering @66 +17:59:01.716921 version@stat F·[] S·0B[] Sc·[] +17:59:01.720432 db@janitor F·2 G·0 +17:59:01.720442 db@open done T·3.774542ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.866907 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.866974 version@stat F·[] S·0B[] Sc·[] +05:10:40.866984 db@open opening +05:10:40.867009 journal@recovery F·1 +05:10:40.867092 journal@recovery recovering @68 +05:10:40.867215 version@stat F·[] S·0B[] Sc·[] +05:10:40.871101 db@janitor F·2 G·0 +05:10:40.871109 db@open done T·4.121771ms +=============== Oct 17, 2024 (UTC) =============== +06:44:58.611297 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.611385 version@stat F·[] S·0B[] Sc·[] +06:44:58.611394 db@open opening +06:44:58.611420 journal@recovery F·1 +06:44:58.611708 journal@recovery recovering @70 +06:44:58.613594 version@stat F·[] S·0B[] Sc·[] +06:44:58.621731 db@janitor F·2 G·0 +06:44:58.621742 db@open done T·10.34492ms +=============== Oct 17, 2024 (UTC) =============== +06:45:46.593245 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.593314 version@stat F·[] S·0B[] Sc·[] +06:45:46.593325 db@open opening +06:45:46.593352 journal@recovery F·1 +06:45:46.595203 journal@recovery recovering @72 +06:45:46.597207 version@stat F·[] S·0B[] Sc·[] +06:45:46.599821 db@janitor F·2 G·0 +06:45:46.599833 db@open done T·6.503955ms +=============== Oct 17, 2024 (UTC) =============== +06:47:26.215962 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.216036 version@stat F·[] S·0B[] Sc·[] +06:47:26.216047 db@open opening +06:47:26.216083 journal@recovery F·1 +06:47:26.217966 journal@recovery recovering @74 +06:47:26.220147 version@stat F·[] S·0B[] Sc·[] +06:47:26.222797 db@janitor F·2 G·0 +06:47:26.222807 db@open done T·6.756568ms diff --git a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/MANIFEST-000077 b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/MANIFEST-000077 new file mode 100644 index 0000000000..dd7fb6cd3d Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/MANIFEST-000077 differ diff --git a/.docker/container-state/atom-testnet-data/data/state.db/000129.ldb b/.docker/container-state/atom-testnet-data/data/state.db/000129.ldb new file mode 100644 index 0000000000..b38bc5d97d Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/state.db/000129.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/state.db/000130.log b/.docker/container-state/atom-testnet-data/data/state.db/000130.log new file mode 100644 index 0000000000..bbb9c9ebd2 Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/state.db/000130.log differ diff --git a/.docker/container-state/atom-testnet-data/data/state.db/000132.ldb b/.docker/container-state/atom-testnet-data/data/state.db/000132.ldb new file mode 100644 index 0000000000..e589a7bb5d Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/state.db/000132.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/state.db/CURRENT b/.docker/container-state/atom-testnet-data/data/state.db/CURRENT new file mode 100644 index 0000000000..d5731e9f98 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/state.db/CURRENT @@ -0,0 +1 @@ +MANIFEST-000131 diff --git a/.docker/container-state/atom-testnet-data/data/state.db/CURRENT.bak b/.docker/container-state/atom-testnet-data/data/state.db/CURRENT.bak new file mode 100644 index 0000000000..224d52afe7 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/state.db/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000127 diff --git a/.docker/container-state/atom-testnet-data/data/state.db/LOCK b/.docker/container-state/atom-testnet-data/data/state.db/LOCK new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/atom-testnet-data/data/state.db/LOG b/.docker/container-state/atom-testnet-data/data/state.db/LOG new file mode 100644 index 0000000000..333f86a5c7 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/state.db/LOG @@ -0,0 +1,507 @@ +=============== Jun 3, 2024 (+03) =============== +14:44:32.491582 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +14:44:32.493405 db@open opening +14:44:32.493614 version@stat F·[] S·0B[] Sc·[] +14:44:32.494820 db@janitor F·2 G·0 +14:44:32.494828 db@open done T·1.416442ms +=============== Jun 6, 2024 (+03) =============== +18:04:49.066239 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:04:49.066302 version@stat F·[] S·0B[] Sc·[] +18:04:49.066312 db@open opening +18:04:49.066337 journal@recovery F·1 +18:04:49.068180 journal@recovery recovering @1 +18:04:49.069352 memdb@flush created L0@2 N·286 S·52KiB "abc..y:1,v7":"val..y:9,v39" +18:04:49.071268 version@stat F·[1] S·52KiB[52KiB] Sc·[0.25] +18:04:49.073814 db@janitor F·3 G·0 +18:04:49.073824 db@open done T·7.508465ms +=============== Jun 6, 2024 (+03) =============== +18:09:26.417763 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:09:26.417850 version@stat F·[1] S·52KiB[52KiB] Sc·[0.25] +18:09:26.417860 db@open opening +18:09:26.417886 journal@recovery F·1 +18:09:26.419730 journal@recovery recovering @3 +18:09:26.420961 memdb@flush created L0@5 N·276 S·49KiB "abc..100,v504":"val..:99,v491" +18:09:26.421071 version@stat F·[2] S·102KiB[102KiB] Sc·[0.50] +18:09:26.423761 db@janitor F·4 G·0 +18:09:26.423776 db@open done T·5.912201ms +=============== Jun 6, 2024 (+03) =============== +19:18:28.702459 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +19:18:28.702524 version@stat F·[2] S·102KiB[102KiB] Sc·[0.50] +19:18:28.702533 db@open opening +19:18:28.702559 journal@recovery F·1 +19:18:28.704423 journal@recovery recovering @6 +19:18:28.705342 memdb@flush created L0@8 N·136 S·29KiB "abc..112,v566":"val..140,v698" +19:18:28.705454 version@stat F·[3] S·132KiB[132KiB] Sc·[0.75] +19:18:28.707895 db@janitor F·5 G·0 +19:18:28.707904 db@open done T·5.368658ms +=============== Jun 6, 2024 (+03) =============== +19:29:57.942395 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +19:29:57.942467 version@stat F·[3] S·132KiB[132KiB] Sc·[0.75] +19:29:57.942476 db@open opening +19:29:57.942504 journal@recovery F·1 +19:29:57.943531 journal@recovery recovering @9 +19:29:57.944488 memdb@flush created L0@11 N·21 S·5KiB "abc..139,v703":"val..144,v720" +19:29:57.946450 version@stat F·[4] S·137KiB[137KiB] Sc·[1.00] +19:29:57.948955 db@janitor F·6 G·0 +19:29:57.948966 db@open done T·6.487446ms +19:29:57.949037 table@compaction L0·4 -> L1·0 S·137KiB Q·723 +19:29:57.950927 table@build created L1@14 N·433 S·59KiB "abc..y:1,v7":"val..:99,v491" +19:29:57.950976 version@stat F·[0 1] S·59KiB[0B 59KiB] Sc·[0.00 0.00] +19:29:57.951995 table@compaction committed F-3 S-78KiB Ke·0 D·286 T·2.948286ms +19:29:57.952048 table@remove removed @8 +19:29:57.952125 table@remove removed @5 +19:29:57.952159 table@remove removed @2 +=============== Jun 10, 2024 (UTC) =============== +10:09:48.940254 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:09:48.940330 version@stat F·[0 1] S·59KiB[0B 59KiB] Sc·[0.00 0.00] +10:09:48.940340 db@open opening +10:09:48.940368 journal@recovery F·1 +10:09:48.940464 journal@recovery recovering @12 +10:09:48.941238 memdb@flush created L0@15 N·41 S·7KiB "abc..143,v725":"val..152,v762" +10:09:48.941531 version@stat F·[1 1] S·66KiB[7KiB 59KiB] Sc·[0.25 0.00] +10:09:48.944883 db@janitor F·5 G·1 +10:09:48.944890 db@janitor removing table-11 +10:09:48.944923 db@open done T·4.579157ms +10:12:34.186166 table@compaction L0·1 -> L1·1 S·66KiB Q·926 +10:12:34.187897 table@build created L1@18 N·457 S·61KiB "abc..y:1,v7":"val..:99,v491" +10:12:34.187919 version@stat F·[0 1] S·61KiB[0B 61KiB] Sc·[0.00 0.00] +10:12:34.189017 table@compaction committed F-1 S-4KiB Ke·0 D·17 T·2.830493ms +10:12:34.189085 table@remove removed @14 +=============== Jun 10, 2024 (UTC) =============== +10:19:47.924141 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:19:47.924233 version@stat F·[0 1] S·61KiB[0B 61KiB] Sc·[0.00 0.00] +10:19:47.924245 db@open opening +10:19:47.924272 journal@recovery F·1 +10:19:47.924395 journal@recovery recovering @16 +10:19:47.926846 memdb@flush created L0@19 N·586 S·100KiB "abc..151,v767":"val..269,v1349" +10:19:47.928660 version@stat F·[1 1] S·162KiB[100KiB 61KiB] Sc·[0.25 0.00] +10:19:47.931422 db@janitor F·5 G·1 +10:19:47.931429 db@janitor removing table-15 +10:19:47.931467 db@open done T·7.218481ms +=============== Jun 10, 2024 (UTC) =============== +10:20:11.781006 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:20:11.781130 version@stat F·[1 1] S·162KiB[100KiB 61KiB] Sc·[0.25 0.00] +10:20:11.781142 db@open opening +10:20:11.781176 journal@recovery F·1 +10:20:11.781282 journal@recovery recovering @20 +10:20:11.782033 memdb@flush created L0@22 N·1 S·141B "off..Key,v1353":"off..Key,v1353" +10:20:11.782147 version@stat F·[2 1] S·162KiB[100KiB 61KiB] Sc·[0.50 0.00] +10:20:11.784717 db@janitor F·5 G·0 +10:20:11.784729 db@open done T·3.58287ms +10:22:57.025785 table@compaction L0·2 -> L1·1 S·162KiB Q·1515 +10:22:57.027672 table@build created L1@25 N·808 S·101KiB "abc..y:1,v7":"val..:99,v491" +10:22:57.027692 version@stat F·[0 1] S·101KiB[0B 101KiB] Sc·[0.00 0.00] +10:22:57.028787 table@compaction committed F-2 S-60KiB Ke·0 D·236 T·2.981815ms +10:22:57.028860 table@remove removed @19 +10:22:57.028904 table@remove removed @18 +=============== Jun 10, 2024 (UTC) =============== +10:23:28.327557 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:23:28.327626 version@stat F·[0 1] S·101KiB[0B 101KiB] Sc·[0.00 0.00] +10:23:28.327637 db@open opening +10:23:28.327666 journal@recovery F·1 +10:23:28.327797 journal@recovery recovering @23 +10:23:28.329167 memdb@flush created L0@26 N·171 S·29KiB "abc..268,v1356":"val..303,v1523" +10:23:28.331040 version@stat F·[1 1] S·131KiB[29KiB 101KiB] Sc·[0.25 0.00] +10:23:28.333638 db@janitor F·5 G·1 +10:23:28.333646 db@janitor removing table-22 +10:23:28.333680 db@open done T·6.038971ms +10:26:13.556090 table@compaction L0·1 -> L1·1 S·131KiB Q·1687 +10:26:13.559311 table@build created L1@29 N·910 S·113KiB "abc..y:1,v7":"val..:99,v491" +10:26:13.559335 version@stat F·[0 1] S·113KiB[0B 113KiB] Sc·[0.00 0.00] +10:26:13.560452 table@compaction committed F-1 S-17KiB Ke·0 D·69 T·4.339276ms +10:26:13.560543 table@remove removed @25 +=============== Jun 10, 2024 (UTC) =============== +10:27:08.506132 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:27:08.506230 version@stat F·[0 1] S·113KiB[0B 113KiB] Sc·[0.00 0.00] +10:27:08.506243 db@open opening +10:27:08.506274 journal@recovery F·1 +10:27:08.506368 journal@recovery recovering @27 +10:27:08.507543 memdb@flush created L0@30 N·206 S·38KiB "abc..302,v1528":"val..344,v1730" +10:27:08.507655 version@stat F·[1 1] S·151KiB[38KiB 113KiB] Sc·[0.25 0.00] +10:27:08.512040 db@janitor F·5 G·1 +10:27:08.512047 db@janitor removing table-26 +10:27:08.512086 db@open done T·5.839899ms +=============== Jun 10, 2024 (UTC) =============== +10:30:28.120211 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:30:28.120282 version@stat F·[1 1] S·151KiB[38KiB 113KiB] Sc·[0.25 0.00] +10:30:28.120292 db@open opening +10:30:28.120319 journal@recovery F·1 +10:30:28.120407 journal@recovery recovering @31 +10:30:28.121322 memdb@flush created L0@33 N·101 S·17KiB "abc..343,v1735":"val..364,v1832" +10:30:28.121427 version@stat F·[2 1] S·169KiB[56KiB 113KiB] Sc·[0.50 0.00] +10:30:28.124021 db@janitor F·5 G·0 +10:30:28.124031 db@open done T·3.734811ms +10:32:58.327355 table@compaction L0·2 -> L1·1 S·169KiB Q·1981 +10:32:58.329351 table@build created L1@36 N·1093 S·135KiB "abc..y:1,v7":"val..:99,v491" +10:32:58.329372 version@stat F·[0 1] S·135KiB[0B 135KiB] Sc·[0.00 0.00] +10:32:58.330455 table@compaction committed F-2 S-33KiB Ke·0 D·124 T·3.080357ms +10:32:58.330520 table@remove removed @30 +10:32:58.330570 table@remove removed @29 +=============== Jun 10, 2024 (UTC) =============== +11:11:44.697359 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:11:44.697432 version@stat F·[0 1] S·135KiB[0B 135KiB] Sc·[0.00 0.00] +11:11:44.697443 db@open opening +11:11:44.697472 journal@recovery F·1 +11:11:44.697661 journal@recovery recovering @34 +11:11:44.699193 memdb@flush created L0@37 N·251 S·43KiB "abc..363,v1837":"val..414,v2084" +11:11:44.701040 version@stat F·[1 1] S·179KiB[43KiB 135KiB] Sc·[0.25 0.00] +11:11:44.703734 db@janitor F·5 G·1 +11:11:44.703742 db@janitor removing table-33 +11:11:44.703778 db@open done T·6.331643ms +11:12:41.510444 table@compaction L0·1 -> L1·1 S·179KiB Q·2143 +11:12:41.514879 table@build created L1@40 N·1243 S·152KiB "abc..y:1,v7":"val..:99,v491" +11:12:41.514902 version@stat F·[0 1] S·152KiB[0B 152KiB] Sc·[0.00 0.00] +11:12:41.515487 table@compaction committed F-1 S-26KiB Ke·0 D·101 T·5.017072ms +11:12:41.515573 table@remove removed @36 +=============== Jun 10, 2024 (UTC) =============== +11:15:00.481126 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:15:00.481205 version@stat F·[0 1] S·152KiB[0B 152KiB] Sc·[0.00 0.00] +11:15:00.481223 db@open opening +11:15:00.481254 journal@recovery F·1 +11:15:00.481345 journal@recovery recovering @38 +11:15:00.482402 memdb@flush created L0@41 N·121 S·44KiB "abc..413,v2089":"val..438,v2206" +11:15:00.482509 version@stat F·[1 1] S·197KiB[44KiB 152KiB] Sc·[0.25 0.00] +11:15:00.485050 db@janitor F·5 G·1 +11:15:00.485057 db@janitor removing table-37 +11:15:00.485097 db@open done T·3.870602ms +=============== Jun 10, 2024 (UTC) =============== +11:21:08.307060 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:21:08.307126 version@stat F·[1 1] S·197KiB[44KiB 152KiB] Sc·[0.25 0.00] +11:21:08.307136 db@open opening +11:21:08.307163 journal@recovery F·1 +11:21:08.307262 journal@recovery recovering @42 +11:21:08.307965 memdb@flush created L0@44 N·1 S·141B "off..Key,v2210":"off..Key,v2210" +11:21:08.308073 version@stat F·[2 1] S·197KiB[44KiB 152KiB] Sc·[0.50 0.00] +11:21:08.310609 db@janitor F·5 G·0 +11:21:08.310619 db@open done T·3.478859ms +=============== Jun 10, 2024 (UTC) =============== +11:22:28.248069 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:22:28.248144 version@stat F·[2 1] S·197KiB[44KiB 152KiB] Sc·[0.50 0.00] +11:22:28.248155 db@open opening +11:22:28.248194 journal@recovery F·1 +11:22:28.248271 journal@recovery recovering @45 +11:22:28.248990 memdb@flush created L0@47 N·6 S·1KiB "abc..437,v2213":"val..439,v2215" +11:22:28.249101 version@stat F·[3 1] S·198KiB[46KiB 152KiB] Sc·[0.75 0.00] +11:22:28.252120 db@janitor F·6 G·0 +11:22:28.252130 db@open done T·3.971993ms +=============== Jun 10, 2024 (UTC) =============== +11:23:15.340458 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:23:15.340532 version@stat F·[3 1] S·198KiB[46KiB 152KiB] Sc·[0.75 0.00] +11:23:15.340542 db@open opening +11:23:15.340572 journal@recovery F·1 +11:23:15.340661 journal@recovery recovering @48 +11:23:15.341449 memdb@flush created L0@50 N·26 S·5KiB "abc..438,v2220":"val..444,v2242" +11:23:15.341565 version@stat F·[4 1] S·203KiB[51KiB 152KiB] Sc·[1.00 0.00] +11:23:15.344531 db@janitor F·7 G·0 +11:23:15.344549 db@open done T·4.003893ms +11:23:15.344567 table@compaction L0·4 -> L1·1 S·203KiB Q·2245 +11:23:15.347824 table@build created L1@53 N·1333 S·174KiB "abc..y:1,v7":"val..:99,v491" +11:23:15.347850 version@stat F·[0 1] S·174KiB[0B 174KiB] Sc·[0.00 0.00] +11:23:15.348412 table@compaction committed F-4 S-29KiB Ke·0 D·64 T·3.829602ms +11:23:15.348473 table@remove removed @47 +11:23:15.348504 table@remove removed @44 +11:23:15.348541 table@remove removed @41 +11:23:15.348595 table@remove removed @40 +=============== Jun 10, 2024 (UTC) =============== +11:29:08.709730 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:29:08.709913 version@stat F·[0 1] S·174KiB[0B 174KiB] Sc·[0.00 0.00] +11:29:08.709928 db@open opening +11:29:08.709989 journal@recovery F·1 +11:29:08.712637 journal@recovery recovering @51 +11:29:08.714167 memdb@flush created L0@54 N·331 S·56KiB "abc..443,v2247":"val..510,v2574" +11:29:08.714305 version@stat F·[1 1] S·231KiB[56KiB 174KiB] Sc·[0.25 0.00] +11:29:08.717361 db@janitor F·5 G·1 +11:29:08.717369 db@janitor removing table-50 +11:29:08.717406 db@open done T·7.467142ms +=============== Jun 10, 2024 (UTC) =============== +11:30:42.387877 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:42.388005 version@stat F·[1 1] S·231KiB[56KiB 174KiB] Sc·[0.25 0.00] +11:30:42.388020 db@open opening +11:30:42.388052 journal@recovery F·1 +11:30:42.388272 journal@recovery recovering @55 +11:30:42.389671 memdb@flush created L0@57 N·86 S·15KiB "abc..509,v2579":"val..527,v2661" +11:30:42.391571 version@stat F·[2 1] S·246KiB[72KiB 174KiB] Sc·[0.50 0.00] +11:30:42.400050 db@janitor F·5 G·0 +11:30:42.400066 db@open done T·12.040961ms +=============== Jun 10, 2024 (UTC) =============== +11:30:55.647230 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:55.647311 version@stat F·[2 1] S·246KiB[72KiB 174KiB] Sc·[0.50 0.00] +11:30:55.647324 db@open opening +11:30:55.647355 journal@recovery F·1 +11:30:55.647502 journal@recovery recovering @58 +11:30:55.648559 memdb@flush created L0@60 N·6 S·1KiB "abc..526,v2666":"val..528,v2668" +11:30:55.648784 version@stat F·[3 1] S·248KiB[73KiB 174KiB] Sc·[0.75 0.00] +11:30:55.651370 db@janitor F·6 G·0 +11:30:55.651383 db@open done T·4.054084ms +11:33:25.878916 table@compaction L0·3 -> L1·1 S·248KiB Q·2817 +11:33:25.881634 table@build created L1@63 N·1585 S·203KiB "abc..y:1,v7":"val..:99,v491" +11:33:25.881656 version@stat F·[0 1] S·203KiB[0B 203KiB] Sc·[0.00 0.00] +11:33:25.882248 table@compaction committed F-3 S-44KiB Ke·0 D·171 T·3.312488ms +11:33:25.882310 table@remove removed @57 +11:33:25.882406 table@remove removed @54 +11:33:25.882470 table@remove removed @53 +=============== Jun 10, 2024 (UTC) =============== +11:36:36.174685 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:36:36.174762 version@stat F·[0 1] S·203KiB[0B 203KiB] Sc·[0.00 0.00] +11:36:36.174775 db@open opening +11:36:36.174805 journal@recovery F·1 +11:36:36.176685 journal@recovery recovering @61 +11:36:36.177965 memdb@flush created L0@64 N·301 S·52KiB "abc..527,v2673":"val..588,v2970" +11:36:36.178103 version@stat F·[1 1] S·255KiB[52KiB 203KiB] Sc·[0.25 0.00] +11:36:36.180802 db@janitor F·5 G·1 +11:36:36.180812 db@janitor removing table-60 +11:36:36.180845 db@open done T·6.065931ms +11:39:21.416169 table@compaction L0·1 -> L1·1 S·255KiB Q·3134 +11:39:21.419332 table@build created L1@67 N·1765 S·224KiB "abc..y:1,v7":"val..:99,v491" +11:39:21.419354 version@stat F·[0 1] S·224KiB[0B 224KiB] Sc·[0.00 0.00] +11:39:21.420882 table@compaction committed F-1 S-31KiB Ke·0 D·121 T·4.69133ms +11:39:21.420976 table@remove removed @63 +=============== Jun 10, 2024 (UTC) =============== +12:01:05.065456 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:01:05.065554 version@stat F·[0 1] S·224KiB[0B 224KiB] Sc·[0.00 0.00] +12:01:05.065566 db@open opening +12:01:05.065600 journal@recovery F·1 +12:01:05.065686 journal@recovery recovering @65 +12:01:05.066980 memdb@flush created L0@68 N·251 S·43KiB "abc..587,v2975":"val..638,v3222" +12:01:05.067116 version@stat F·[1 1] S·267KiB[43KiB 224KiB] Sc·[0.25 0.00] +12:01:05.069794 db@janitor F·5 G·1 +12:01:05.069803 db@janitor removing table-64 +12:01:05.069856 db@open done T·4.285068ms +=============== Jun 10, 2024 (UTC) =============== +12:03:00.050214 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:00.050294 version@stat F·[1 1] S·267KiB[43KiB 224KiB] Sc·[0.25 0.00] +12:03:00.050308 db@open opening +12:03:00.050339 journal@recovery F·1 +12:03:00.050425 journal@recovery recovering @69 +12:03:00.051162 memdb@flush created L0@71 N·16 S·2KiB "abc..637,v3227":"val..641,v3239" +12:03:00.051282 version@stat F·[2 1] S·270KiB[46KiB 224KiB] Sc·[0.50 0.00] +12:03:00.053885 db@janitor F·5 G·0 +12:03:00.053900 db@open done T·3.587902ms +=============== Jun 10, 2024 (UTC) =============== +12:03:19.759315 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:19.759390 version@stat F·[2 1] S·270KiB[46KiB 224KiB] Sc·[0.50 0.00] +12:03:19.759402 db@open opening +12:03:19.759435 journal@recovery F·1 +12:03:19.761256 journal@recovery recovering @72 +12:03:19.761978 memdb@flush created L0@74 N·11 S·2KiB "abc..640,v3244":"val..643,v3251" +12:03:19.762093 version@stat F·[3 1] S·272KiB[48KiB 224KiB] Sc·[0.75 0.00] +12:03:19.764654 db@janitor F·6 G·0 +12:03:19.764670 db@open done T·5.263086ms +=============== Jun 10, 2024 (UTC) =============== +12:04:23.852929 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:04:23.853006 version@stat F·[3 1] S·272KiB[48KiB 224KiB] Sc·[0.75 0.00] +12:04:23.853018 db@open opening +12:04:23.853052 journal@recovery F·1 +12:04:23.853131 journal@recovery recovering @75 +12:04:23.853850 memdb@flush created L0@77 N·6 S·1KiB "abc..642,v3256":"val..644,v3258" +12:04:23.853976 version@stat F·[4 1] S·274KiB[49KiB 224KiB] Sc·[1.00 0.00] +12:04:23.856578 db@janitor F·7 G·0 +12:04:23.856589 db@open done T·3.567482ms +12:04:23.856623 table@compaction L0·4 -> L1·1 S·274KiB Q·3261 +12:04:23.861057 table@build created L1@80 N·1933 S·243KiB "abc..y:1,v7":"val..:99,v491" +12:04:23.861100 version@stat F·[0 1] S·243KiB[0B 243KiB] Sc·[0.00 0.00] +12:04:23.862613 table@compaction committed F-4 S-30KiB Ke·0 D·116 T·5.965383ms +12:04:23.862722 table@remove removed @74 +12:04:23.862756 table@remove removed @71 +12:04:23.862795 table@remove removed @68 +12:04:23.862866 table@remove removed @67 +=============== Jun 10, 2024 (UTC) =============== +12:07:46.935525 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:07:46.935601 version@stat F·[0 1] S·243KiB[0B 243KiB] Sc·[0.00 0.00] +12:07:46.935612 db@open opening +12:07:46.935642 journal@recovery F·1 +12:07:46.935887 journal@recovery recovering @78 +12:07:46.937468 memdb@flush created L0@81 N·1 S·141B "off..Key,v3262":"off..Key,v3262" +12:07:46.937734 version@stat F·[1 1] S·243KiB[141B 243KiB] Sc·[0.25 0.00] +12:07:46.940329 db@janitor F·5 G·1 +12:07:46.940338 db@janitor removing table-77 +12:07:46.940372 db@open done T·4.754782ms +=============== Jun 10, 2024 (UTC) =============== +12:08:02.157116 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:08:02.157193 version@stat F·[1 1] S·243KiB[141B 243KiB] Sc·[0.25 0.00] +12:08:02.157206 db@open opening +12:08:02.157234 journal@recovery F·1 +12:08:02.157313 journal@recovery recovering @82 +12:08:02.158010 memdb@flush created L0@84 N·1 S·141B "off..Key,v3264":"off..Key,v3264" +12:08:02.158127 version@stat F·[2 1] S·243KiB[282B 243KiB] Sc·[0.50 0.00] +12:08:02.160671 db@janitor F·5 G·0 +12:08:02.160685 db@open done T·3.475091ms +=============== Jun 10, 2024 (UTC) =============== +12:27:21.485205 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:27:21.485280 version@stat F·[2 1] S·243KiB[282B 243KiB] Sc·[0.50 0.00] +12:27:21.485292 db@open opening +12:27:21.485322 journal@recovery F·1 +12:27:21.485414 journal@recovery recovering @85 +12:27:21.486106 memdb@flush created L0@87 N·1 S·141B "off..Key,v3266":"off..Key,v3266" +12:27:21.486222 version@stat F·[3 1] S·243KiB[423B 243KiB] Sc·[0.75 0.00] +12:27:21.488835 db@janitor F·6 G·0 +12:27:21.488846 db@open done T·3.549981ms +=============== Jun 10, 2024 (UTC) =============== +12:56:43.855222 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:56:43.855290 version@stat F·[3 1] S·243KiB[423B 243KiB] Sc·[0.75 0.00] +12:56:43.855302 db@open opening +12:56:43.855333 journal@recovery F·1 +12:56:43.857248 journal@recovery recovering @88 +12:56:43.857943 memdb@flush created L0@90 N·1 S·141B "off..Key,v3268":"off..Key,v3268" +12:56:43.858064 version@stat F·[4 1] S·244KiB[564B 243KiB] Sc·[1.00 0.00] +12:56:43.860558 db@janitor F·7 G·0 +12:56:43.860568 db@open done T·5.262286ms +12:56:43.860590 table@compaction L0·4 -> L1·1 S·244KiB Q·3269 +12:56:43.863695 table@build created L1@93 N·1933 S·243KiB "abc..y:1,v7":"val..:99,v491" +12:56:43.863715 version@stat F·[0 1] S·243KiB[0B 243KiB] Sc·[0.00 0.00] +12:56:43.865868 table@compaction committed F-4 S-564B Ke·0 D·4 T·5.258606ms +12:56:43.865939 table@remove removed @87 +12:56:43.866030 table@remove removed @84 +12:56:43.866063 table@remove removed @81 +12:56:43.866214 table@remove removed @80 +=============== Jun 10, 2024 (UTC) =============== +12:57:38.429067 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:57:38.429136 version@stat F·[0 1] S·243KiB[0B 243KiB] Sc·[0.00 0.00] +12:57:38.429149 db@open opening +12:57:38.429177 journal@recovery F·1 +12:57:38.429369 journal@recovery recovering @91 +12:57:38.430151 memdb@flush created L0@94 N·1 S·141B "off..Key,v3270":"off..Key,v3270" +12:57:38.430364 version@stat F·[1 1] S·243KiB[141B 243KiB] Sc·[0.25 0.00] +12:57:38.432954 db@janitor F·5 G·1 +12:57:38.432969 db@janitor removing table-90 +12:57:38.433003 db@open done T·3.849903ms +=============== Jun 10, 2024 (UTC) =============== +12:58:01.523272 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:01.523344 version@stat F·[1 1] S·243KiB[141B 243KiB] Sc·[0.25 0.00] +12:58:01.523357 db@open opening +12:58:01.523384 journal@recovery F·1 +12:58:01.523485 journal@recovery recovering @95 +12:58:01.524177 memdb@flush created L0@97 N·1 S·141B "off..Key,v3272":"off..Key,v3272" +12:58:01.524295 version@stat F·[2 1] S·243KiB[282B 243KiB] Sc·[0.50 0.00] +12:58:01.526870 db@janitor F·5 G·0 +12:58:01.526886 db@open done T·3.525131ms +=============== Jun 10, 2024 (UTC) =============== +12:58:49.043806 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:49.043878 version@stat F·[2 1] S·243KiB[282B 243KiB] Sc·[0.50 0.00] +12:58:49.043891 db@open opening +12:58:49.043921 journal@recovery F·1 +12:58:49.044000 journal@recovery recovering @98 +12:58:49.044711 memdb@flush created L0@100 N·1 S·141B "off..Key,v3274":"off..Key,v3274" +12:58:49.044831 version@stat F·[3 1] S·243KiB[423B 243KiB] Sc·[0.75 0.00] +12:58:49.047427 db@janitor F·6 G·0 +12:58:49.047442 db@open done T·3.546651ms +=============== Jun 10, 2024 (UTC) =============== +12:59:05.182866 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:05.182939 version@stat F·[3 1] S·243KiB[423B 243KiB] Sc·[0.75 0.00] +12:59:05.182949 db@open opening +12:59:05.182978 journal@recovery F·1 +12:59:05.183078 journal@recovery recovering @101 +12:59:05.183778 memdb@flush created L0@103 N·1 S·141B "off..Key,v3276":"off..Key,v3276" +12:59:05.183898 version@stat F·[4 1] S·244KiB[564B 243KiB] Sc·[1.00 0.00] +12:59:05.187343 db@janitor F·7 G·0 +12:59:05.187358 db@open done T·4.405238ms +12:59:05.187383 table@compaction L0·4 -> L1·1 S·244KiB Q·3277 +12:59:05.191222 table@build created L1@106 N·1933 S·243KiB "abc..y:1,v7":"val..:99,v491" +12:59:05.191248 version@stat F·[0 1] S·243KiB[0B 243KiB] Sc·[0.00 0.00] +12:59:05.192786 table@compaction committed F-4 S-564B Ke·0 D·4 T·5.387817ms +12:59:05.192898 table@remove removed @100 +12:59:05.192932 table@remove removed @97 +12:59:05.192960 table@remove removed @94 +12:59:05.193036 table@remove removed @93 +=============== Jun 10, 2024 (UTC) =============== +12:59:32.462104 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:32.462234 version@stat F·[0 1] S·243KiB[0B 243KiB] Sc·[0.00 0.00] +12:59:32.462248 db@open opening +12:59:32.462279 journal@recovery F·1 +12:59:32.462364 journal@recovery recovering @104 +12:59:32.463150 memdb@flush created L0@107 N·6 S·1KiB "abc..643,v3279":"val..645,v3281" +12:59:32.463288 version@stat F·[1 1] S·244KiB[1KiB 243KiB] Sc·[0.25 0.00] +12:59:32.467320 db@janitor F·5 G·1 +12:59:32.467329 db@janitor removing table-103 +12:59:32.467362 db@open done T·5.109474ms +=============== Jul 3, 2024 (UTC) =============== +17:52:58.392967 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:52:58.393039 version@stat F·[1 1] S·244KiB[1KiB 243KiB] Sc·[0.25 0.00] +17:52:58.393050 db@open opening +17:52:58.393077 journal@recovery F·1 +17:52:58.393169 journal@recovery recovering @108 +17:52:58.393872 memdb@flush created L0@110 N·1 S·141B "off..Key,v3285":"off..Key,v3285" +17:52:58.393983 version@stat F·[2 1] S·244KiB[1KiB 243KiB] Sc·[0.50 0.00] +17:52:58.396491 db@janitor F·5 G·0 +17:52:58.396505 db@open done T·3.451089ms +17:55:33.613329 table@compaction L0·2 -> L1·1 S·244KiB Q·3437 +17:55:33.616483 table@build created L1@113 N·1936 S·243KiB "abc..y:1,v7":"val..:99,v491" +17:55:33.616505 version@stat F·[0 1] S·243KiB[0B 243KiB] Sc·[0.00 0.00] +17:55:33.618025 table@compaction committed F-2 S-1KiB Ke·0 D·4 T·4.673628ms +17:55:33.618089 table@remove removed @107 +17:55:33.618160 table@remove removed @106 +=============== Jul 3, 2024 (UTC) =============== +17:59:01.761567 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:59:01.761644 version@stat F·[0 1] S·243KiB[0B 243KiB] Sc·[0.00 0.00] +17:59:01.761656 db@open opening +17:59:01.761687 journal@recovery F·1 +17:59:01.761781 journal@recovery recovering @111 +17:59:01.763581 memdb@flush created L0@114 N·346 S·83KiB "abc..644,v3288":"val..714,v3630" +17:59:01.763748 version@stat F·[1 1] S·327KiB[83KiB 243KiB] Sc·[0.25 0.00] +17:59:01.766489 db@janitor F·5 G·1 +17:59:01.766499 db@janitor removing table-110 +17:59:01.766531 db@open done T·4.87064ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.915711 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.915810 version@stat F·[1 1] S·327KiB[83KiB 243KiB] Sc·[0.25 0.00] +05:10:40.915825 db@open opening +05:10:40.915868 journal@recovery F·1 +05:10:40.918284 journal@recovery recovering @115 +05:10:40.919542 memdb@flush created L0@117 N·26 S·5KiB "abc..713,v3635":"val..719,v3657" +05:10:40.919668 version@stat F·[2 1] S·332KiB[88KiB 243KiB] Sc·[0.50 0.00] +05:10:40.923785 db@janitor F·5 G·0 +05:10:40.923796 db@open done T·7.966161ms +05:11:00.958934 table@compaction L0·2 -> L1·1 S·332KiB Q·3676 +05:11:00.962394 table@build created L1@120 N·2158 S·280KiB "abc..y:1,v7":"val..:99,v491" +05:11:00.962424 version@stat F·[0 1] S·280KiB[0B 280KiB] Sc·[0.00 0.00] +05:11:00.964049 table@compaction committed F-2 S-51KiB Ke·0 D·150 T·5.093449ms +05:11:00.964119 table@remove removed @114 +05:11:00.964186 table@remove removed @113 +=============== Oct 17, 2024 (UTC) =============== +06:44:58.690228 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.690292 version@stat F·[0 1] S·280KiB[0B 280KiB] Sc·[0.00 0.00] +06:44:58.690301 db@open opening +06:44:58.690327 journal@recovery F·1 +06:44:58.690411 journal@recovery recovering @118 +06:44:58.698458 memdb@flush created L0@121 N·721 S·152KiB "abc..718,v3662":"val..863,v4379" +06:44:58.698595 version@stat F·[1 1] S·433KiB[152KiB 280KiB] Sc·[0.25 0.00] +06:44:58.713491 db@janitor F·5 G·1 +06:44:58.713496 db@janitor removing table-117 +06:44:58.713521 db@open done T·23.21798ms +06:45:33.812957 table@compaction L0·1 -> L1·1 S·433KiB Q·4413 +06:45:33.817194 table@build created L1@124 N·2590 S·345KiB "abc..y:1,v7":"val..:99,v491" +06:45:33.817219 version@stat F·[0 1] S·345KiB[0B 345KiB] Sc·[0.00 0.00] +06:45:33.818740 table@compaction committed F-1 S-88KiB Ke·0 D·289 T·5.758129ms +06:45:33.818856 table@remove removed @120 +=============== Oct 17, 2024 (UTC) =============== +06:45:46.642590 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.642660 version@stat F·[0 1] S·345KiB[0B 345KiB] Sc·[0.00 0.00] +06:45:46.642670 db@open opening +06:45:46.642701 journal@recovery F·1 +06:45:46.642793 journal@recovery recovering @122 +06:45:46.643923 memdb@flush created L0@125 N·36 S·14KiB "abc..862,v4384":"val..870,v4416" +06:45:46.644184 version@stat F·[1 1] S·359KiB[14KiB 345KiB] Sc·[0.25 0.00] +06:45:46.648159 db@janitor F·5 G·1 +06:45:46.648166 db@janitor removing table-121 +06:45:46.648229 db@open done T·5.554767ms +06:46:12.624281 table@compaction L0·1 -> L1·1 S·359KiB Q·4445 +06:46:12.628139 table@build created L1@128 N·2611 S·351KiB "abc..y:1,v7":"val..:99,v491" +06:46:12.628162 version@stat F·[0 1] S·351KiB[0B 351KiB] Sc·[0.00 0.00] +06:46:12.628755 table@compaction committed F-1 S-7KiB Ke·0 D·15 T·4.453088ms +06:46:12.628879 table@remove removed @124 +=============== Oct 17, 2024 (UTC) =============== +06:47:26.269795 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.269871 version@stat F·[0 1] S·351KiB[0B 351KiB] Sc·[0.00 0.00] +06:47:26.269882 db@open opening +06:47:26.269915 journal@recovery F·1 +06:47:26.270011 journal@recovery recovering @126 +06:47:26.270915 memdb@flush created L0@129 N·76 S·13KiB "abc..869,v4421":"val..885,v4493" +06:47:26.271251 version@stat F·[1 1] S·364KiB[13KiB 351KiB] Sc·[0.25 0.00] +06:47:26.274125 db@janitor F·5 G·1 +06:47:26.274133 db@janitor removing table-125 +06:47:26.274169 db@open done T·4.283147ms +06:47:51.337900 table@compaction L0·1 -> L1·1 S·364KiB Q·4522 +06:47:51.342188 table@build created L1@132 N·2656 S·356KiB "abc..y:1,v7":"val..:99,v491" +06:47:51.342209 version@stat F·[0 1] S·356KiB[0B 356KiB] Sc·[0.00 0.00] +06:47:51.343789 table@compaction committed F-1 S-7KiB Ke·0 D·31 T·5.87117ms +06:47:51.343944 table@remove removed @128 diff --git a/.docker/container-state/atom-testnet-data/data/state.db/MANIFEST-000131 b/.docker/container-state/atom-testnet-data/data/state.db/MANIFEST-000131 new file mode 100644 index 0000000000..f5239ae69c Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/state.db/MANIFEST-000131 differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000101.ldb b/.docker/container-state/atom-testnet-data/data/tx_index.db/000101.ldb new file mode 100644 index 0000000000..708a3e9b1f Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/tx_index.db/000101.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000102.ldb b/.docker/container-state/atom-testnet-data/data/tx_index.db/000102.ldb new file mode 100644 index 0000000000..881b5395e2 Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/tx_index.db/000102.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000105.ldb b/.docker/container-state/atom-testnet-data/data/tx_index.db/000105.ldb new file mode 100644 index 0000000000..f6ef7556d8 Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/tx_index.db/000105.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000108.ldb b/.docker/container-state/atom-testnet-data/data/tx_index.db/000108.ldb new file mode 100644 index 0000000000..5e5fa0b9d8 Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/tx_index.db/000108.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000109.log b/.docker/container-state/atom-testnet-data/data/tx_index.db/000109.log new file mode 100644 index 0000000000..d42d24bdef Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/tx_index.db/000109.log differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/CURRENT b/.docker/container-state/atom-testnet-data/data/tx_index.db/CURRENT new file mode 100644 index 0000000000..a451d53d04 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/tx_index.db/CURRENT @@ -0,0 +1 @@ +MANIFEST-000110 diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/CURRENT.bak b/.docker/container-state/atom-testnet-data/data/tx_index.db/CURRENT.bak new file mode 100644 index 0000000000..aecd689375 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/tx_index.db/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000107 diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/LOCK b/.docker/container-state/atom-testnet-data/data/tx_index.db/LOCK new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/LOG b/.docker/container-state/atom-testnet-data/data/tx_index.db/LOG new file mode 100644 index 0000000000..30dd8dbfc5 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/tx_index.db/LOG @@ -0,0 +1,428 @@ +=============== Jun 3, 2024 (+03) =============== +14:44:32.496020 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +14:44:32.497868 db@open opening +14:44:32.498022 version@stat F·[] S·0B[] Sc·[] +14:44:32.498623 db@janitor F·2 G·0 +14:44:32.498647 db@open done T·772.676µs +=============== Jun 6, 2024 (+03) =============== +18:04:49.075260 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:04:49.075321 version@stat F·[] S·0B[] Sc·[] +18:04:49.075331 db@open opening +18:04:49.075355 journal@recovery F·1 +18:04:49.075436 journal@recovery recovering @1 +18:04:49.077025 memdb@flush created L0@2 N·1612 S·25KiB "blo..\x00\x01\x81,v1":"blo..\x01\xc2\xca,v1594" +18:04:49.077135 version@stat F·[1] S·25KiB[25KiB] Sc·[0.25] +18:04:49.079674 db@janitor F·3 G·0 +18:04:49.079684 db@open done T·4.348848ms +=============== Jun 6, 2024 (+03) =============== +18:09:26.424183 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:09:26.424253 version@stat F·[1] S·25KiB[25KiB] Sc·[0.25] +18:09:26.424264 db@open opening +18:09:26.424289 journal@recovery F·1 +18:09:26.424367 journal@recovery recovering @3 +18:09:26.426040 memdb@flush created L0@5 N·1637 S·29KiB "01\x11..\x9f\xa8\xa6,v2873":"\x93NV..\x16b\x1d,v2323" +18:09:26.426155 version@stat F·[2] S·54KiB[54KiB] Sc·[0.50] +18:09:26.435684 db@janitor F·4 G·0 +18:09:26.435696 db@open done T·11.427739ms +=============== Jun 6, 2024 (+03) =============== +19:18:28.708310 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +19:18:28.708381 version@stat F·[2] S·54KiB[54KiB] Sc·[0.50] +19:18:28.708390 db@open opening +19:18:28.708417 journal@recovery F·1 +19:18:28.708491 journal@recovery recovering @6 +19:18:28.709672 memdb@flush created L0@8 N·875 S·18KiB ":cU..\xdaRE,v3772":"\xef\xb9<..\x9a\x1b\x86,v4043" +19:18:28.709802 version@stat F·[3] S·73KiB[73KiB] Sc·[0.75] +19:18:28.712224 db@janitor F·5 G·0 +19:18:28.712232 db@open done T·3.839274ms +=============== Jun 6, 2024 (+03) =============== +19:29:57.949413 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +19:29:57.949480 version@stat F·[3] S·73KiB[73KiB] Sc·[0.75] +19:29:57.949489 db@open opening +19:29:57.949516 journal@recovery F·1 +19:29:57.949600 journal@recovery recovering @9 +19:29:57.950423 memdb@flush created L0@11 N·141 S·4KiB "\x1dU\xbd..\xdfV\x85,v4210":"tx...s$7,v4197" +19:29:57.950541 version@stat F·[4] S·77KiB[77KiB] Sc·[1.00] +19:29:57.953844 db@janitor F·6 G·0 +19:29:57.953856 db@open done T·4.364458ms +19:29:57.953873 table@compaction L0·4 -> L1·0 S·77KiB Q·4269 +19:29:57.956040 table@build created L1@14 N·4265 S·81KiB "\x1dU\xbd..\xdfV\x85,v4210":"\xef\xb9<..\x9a\x1b\x86,v4043" +19:29:57.956057 version@stat F·[0 1] S·81KiB[0B 81KiB] Sc·[0.00 0.00] +19:29:57.957148 table@compaction committed F-3 S+3KiB Ke·0 D·0 T·3.266569ms +19:29:57.957193 table@remove removed @8 +19:29:57.957269 table@remove removed @5 +19:29:57.957299 table@remove removed @2 +=============== Jun 10, 2024 (UTC) =============== +10:09:48.945690 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:09:48.945769 version@stat F·[0 1] S·81KiB[0B 81KiB] Sc·[0.00 0.00] +10:09:48.945780 db@open opening +10:09:48.945810 journal@recovery F·1 +10:09:48.947674 journal@recovery recovering @12 +10:09:48.948524 memdb@flush created L0@15 N·232 S·4KiB "blo..\x01\xc0\x8f,v4270":"blo..\x01\xc0`,v4483" +10:09:48.950420 version@stat F·[1 1] S·85KiB[4KiB 81KiB] Sc·[0.25 0.00] +10:09:48.953250 db@janitor F·5 G·1 +10:09:48.953256 db@janitor removing table-11 +10:09:48.953288 db@open done T·7.503901ms +=============== Jun 10, 2024 (UTC) =============== +10:19:47.931822 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:19:47.931895 version@stat F·[1 1] S·85KiB[4KiB 81KiB] Sc·[0.25 0.00] +10:19:47.931905 db@open opening +10:19:47.931933 journal@recovery F·1 +10:19:47.932025 journal@recovery recovering @16 +10:19:47.934684 memdb@flush created L0@18 N·3393 S·58KiB "blo..\x01\xc0\x97,v4503":"blo..\x01\xc5\xe9,v7877" +10:19:47.934801 version@stat F·[2 1] S·143KiB[62KiB 81KiB] Sc·[0.50 0.00] +10:19:47.937476 db@janitor F·5 G·0 +10:19:47.937486 db@open done T·5.579207ms +=============== Jun 10, 2024 (UTC) =============== +10:20:11.785141 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:20:11.785227 version@stat F·[2 1] S·143KiB[62KiB 81KiB] Sc·[0.50 0.00] +10:20:11.785239 db@open opening +10:20:11.785268 journal@recovery F·1 +10:20:11.785350 journal@recovery recovering @19 +10:20:11.785477 version@stat F·[2 1] S·143KiB[62KiB 81KiB] Sc·[0.50 0.00] +10:20:11.788064 db@janitor F·5 G·0 +10:20:11.788074 db@open done T·2.830793ms +=============== Jun 10, 2024 (UTC) =============== +10:23:28.334052 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:23:28.334123 version@stat F·[2 1] S·143KiB[62KiB 81KiB] Sc·[0.50 0.00] +10:23:28.334134 db@open opening +10:23:28.334166 journal@recovery F·1 +10:23:28.334267 journal@recovery recovering @21 +10:23:28.335538 memdb@flush created L0@23 N·986 S·16KiB "blo..\x01\xc1\f,v7897":"blo..\x01\xc1\xb2,v8864" +10:23:28.335649 version@stat F·[3 1] S·159KiB[78KiB 81KiB] Sc·[0.75 0.00] +10:23:28.338205 db@janitor F·6 G·0 +10:23:28.338216 db@open done T·4.078524ms +=============== Jun 10, 2024 (UTC) =============== +10:27:08.512447 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:27:08.512528 version@stat F·[3 1] S·159KiB[78KiB 81KiB] Sc·[0.75 0.00] +10:27:08.512539 db@open opening +10:27:08.512569 journal@recovery F·1 +10:27:08.512654 journal@recovery recovering @24 +10:27:08.514153 memdb@flush created L0@26 N·1228 S·22KiB "\tW\x04..'%z,v9477":"\xe4\xc1 ..\xbe\xbd\xab,v9879" +10:27:08.514282 version@stat F·[4 1] S·182KiB[100KiB 81KiB] Sc·[1.00 0.00] +10:27:08.516900 db@janitor F·7 G·0 +10:27:08.516914 db@open done T·4.371717ms +10:27:08.516927 table@compaction L0·4 -> L1·1 S·182KiB Q·10112 +10:27:08.521707 table@build created L1@29 N·10104 S·181KiB "\tW\x04..'%z,v9477":"\xef\xb9<..\x9a\x1b\x86,v4043" +10:27:08.521732 version@stat F·[0 1] S·181KiB[0B 181KiB] Sc·[0.00 0.00] +10:27:08.522816 table@compaction committed F-4 S-1KiB Ke·0 D·0 T·5.87477ms +10:27:08.522880 table@remove removed @23 +10:27:08.522964 table@remove removed @18 +10:27:08.522998 table@remove removed @15 +10:27:08.523039 table@remove removed @14 +=============== Jun 10, 2024 (UTC) =============== +10:30:28.124491 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:30:28.124570 version@stat F·[0 1] S·181KiB[0B 181KiB] Sc·[0.00 0.00] +10:30:28.124581 db@open opening +10:30:28.124611 journal@recovery F·1 +10:30:28.124736 journal@recovery recovering @27 +10:30:28.126183 memdb@flush created L0@30 N·580 S·9KiB "blo..\x01\xc1W,v10113":"blo..\x01\xc0\xfc,v10674" +10:30:28.128128 version@stat F·[1 1] S·190KiB[9KiB 181KiB] Sc·[0.25 0.00] +10:30:28.130689 db@janitor F·5 G·1 +10:30:28.130696 db@janitor removing table-26 +10:30:28.130734 db@open done T·6.149542ms +=============== Jun 10, 2024 (UTC) =============== +11:11:44.704217 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:11:44.704299 version@stat F·[1 1] S·190KiB[9KiB 181KiB] Sc·[0.25 0.00] +11:11:44.704312 db@open opening +11:11:44.704346 journal@recovery F·1 +11:11:44.704425 journal@recovery recovering @31 +11:11:44.705964 memdb@flush created L0@33 N·1450 S·23KiB "blo..\x01\xc1k,v10694":"blo..\x01\u0082,v12125" +11:11:44.706086 version@stat F·[2 1] S·214KiB[33KiB 181KiB] Sc·[0.50 0.00] +11:11:44.708711 db@janitor F·5 G·0 +11:11:44.708721 db@open done T·4.405607ms +=============== Jun 10, 2024 (UTC) =============== +11:15:00.485497 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:15:00.485602 version@stat F·[2 1] S·214KiB[33KiB 181KiB] Sc·[0.50 0.00] +11:15:00.485613 db@open opening +11:15:00.485647 journal@recovery F·1 +11:15:00.485799 journal@recovery recovering @34 +11:15:00.487788 memdb@flush created L0@36 N·835 S·36KiB "\x04\x92\xf2..\v,\x8e,v12512":"\xba[\x1e..SVa,v12657" +11:15:00.489774 version@stat F·[3 1] S·251KiB[70KiB 181KiB] Sc·[0.75 0.00] +11:15:00.492389 db@janitor F·6 G·0 +11:15:00.492402 db@open done T·6.785426ms +=============== Jun 10, 2024 (UTC) =============== +11:21:08.310961 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:21:08.311037 version@stat F·[3 1] S·251KiB[70KiB 181KiB] Sc·[0.75 0.00] +11:21:08.311050 db@open opening +11:21:08.311081 journal@recovery F·1 +11:21:08.311156 journal@recovery recovering @37 +11:21:08.311393 version@stat F·[3 1] S·251KiB[70KiB 181KiB] Sc·[0.75 0.00] +11:21:08.314600 db@janitor F·6 G·0 +11:21:08.314610 db@open done T·3.55552ms +=============== Jun 10, 2024 (UTC) =============== +11:22:28.252574 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:22:28.252649 version@stat F·[3 1] S·251KiB[70KiB 181KiB] Sc·[0.75 0.00] +11:22:28.252659 db@open opening +11:22:28.252687 journal@recovery F·1 +11:22:28.252764 journal@recovery recovering @39 +11:22:28.253465 memdb@flush created L0@41 N·29 S·925B "blo..\x01\xc1\xb5,v12981":"blo..\x00\x01\x85,v12991" +11:22:28.253635 version@stat F·[4 1] S·252KiB[70KiB 181KiB] Sc·[1.00 0.00] +11:22:28.256514 db@janitor F·7 G·0 +11:22:28.256526 db@open done T·3.863542ms +11:22:28.256561 table@compaction L0·4 -> L1·1 S·252KiB Q·13010 +11:22:28.262691 table@build created L1@44 N·12998 S·253KiB "\x04\x92\xf2..\v,\x8e,v12512":"\xef\xb9<..\x9a\x1b\x86,v4043" +11:22:28.262719 version@stat F·[0 1] S·253KiB[0B 253KiB] Sc·[0.00 0.00] +11:22:28.263937 table@compaction committed F-4 S+1KiB Ke·0 D·0 T·7.351261ms +11:22:28.264032 table@remove removed @36 +11:22:28.264080 table@remove removed @33 +11:22:28.264127 table@remove removed @30 +11:22:28.264218 table@remove removed @29 +=============== Jun 10, 2024 (UTC) =============== +11:23:15.344988 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:23:15.345083 version@stat F·[0 1] S·253KiB[0B 253KiB] Sc·[0.00 0.00] +11:23:15.345098 db@open opening +11:23:15.345137 journal@recovery F·1 +11:23:15.345324 journal@recovery recovering @42 +11:23:15.346996 memdb@flush created L0@45 N·145 S·2KiB "blo..\x01\xc1\xb6,v13011":"blo..\x00\x01\xb9,v13137" +11:23:15.348877 version@stat F·[1 1] S·256KiB[2KiB 253KiB] Sc·[0.25 0.00] +11:23:15.351409 db@janitor F·5 G·1 +11:23:15.351416 db@janitor removing table-41 +11:23:15.351450 db@open done T·6.348053ms +=============== Jun 10, 2024 (UTC) =============== +11:29:08.717753 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:29:08.717824 version@stat F·[1 1] S·256KiB[2KiB 253KiB] Sc·[0.25 0.00] +11:29:08.717836 db@open opening +11:29:08.717865 journal@recovery F·1 +11:29:08.717942 journal@recovery recovering @46 +11:29:08.719709 memdb@flush created L0@48 N·1914 S·31KiB "blo..\x01\xc1\xbb,v13157":"blo..\x01\xc3R,v15052" +11:29:08.719821 version@stat F·[2 1] S·287KiB[34KiB 253KiB] Sc·[0.50 0.00] +11:29:08.722425 db@janitor F·5 G·0 +11:29:08.722436 db@open done T·4.596628ms +=============== Jun 10, 2024 (UTC) =============== +11:30:42.400488 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:42.400575 version@stat F·[2 1] S·287KiB[34KiB 253KiB] Sc·[0.50 0.00] +11:30:42.400587 db@open opening +11:30:42.400620 journal@recovery F·1 +11:30:42.400705 journal@recovery recovering @49 +11:30:42.401777 memdb@flush created L0@51 N·493 S·8KiB "blo..\x01\xc1\xfd,v15072":"blo..\x01\xc0\xd5,v15546" +11:30:42.401897 version@stat F·[3 1] S·296KiB[42KiB 253KiB] Sc·[0.75 0.00] +11:30:42.404520 db@janitor F·6 G·0 +11:30:42.404533 db@open done T·3.942623ms +=============== Jun 10, 2024 (UTC) =============== +11:30:55.651815 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:55.651923 version@stat F·[3 1] S·296KiB[42KiB 253KiB] Sc·[0.75 0.00] +11:30:55.651938 db@open opening +11:30:55.651978 journal@recovery F·1 +11:30:55.653912 journal@recovery recovering @52 +11:30:55.654664 memdb@flush created L0@54 N·29 S·921B "blo..\x01\xc2\x0e,v15566":"blo..\x00\x01\x85,v15576" +11:30:55.654798 version@stat F·[4 1] S·296KiB[43KiB 253KiB] Sc·[1.00 0.00] +11:30:55.657351 db@janitor F·7 G·0 +11:30:55.657367 db@open done T·5.424386ms +11:30:55.657388 table@compaction L0·4 -> L1·1 S·296KiB Q·15595 +11:30:55.663657 table@build created L1@57 N·15579 S·297KiB "\x04\x92\xf2..\v,\x8e,v12512":"\xef\xb9<..\x9a\x1b\x86,v4043" +11:30:55.663681 version@stat F·[0 1] S·297KiB[0B 297KiB] Sc·[0.00 0.00] +11:30:55.665265 table@compaction committed F-4 S+908B Ke·0 D·0 T·7.858166ms +11:30:55.665342 table@remove removed @51 +11:30:55.665392 table@remove removed @48 +11:30:55.665434 table@remove removed @45 +11:30:55.665529 table@remove removed @44 +=============== Jun 10, 2024 (UTC) =============== +11:36:36.181241 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:36:36.181320 version@stat F·[0 1] S·297KiB[0B 297KiB] Sc·[0.00 0.00] +11:36:36.181333 db@open opening +11:36:36.181362 journal@recovery F·1 +11:36:36.181439 journal@recovery recovering @55 +11:36:36.183160 memdb@flush created L0@58 N·1740 S·28KiB "blo..\x01\xc2\x0f,v15596":"blo..\x01\xc3\x04,v17317" +11:36:36.183288 version@stat F·[1 1] S·326KiB[28KiB 297KiB] Sc·[0.25 0.00] +11:36:36.185945 db@janitor F·5 G·1 +11:36:36.185953 db@janitor removing table-54 +11:36:36.185986 db@open done T·4.648749ms +=============== Jun 10, 2024 (UTC) =============== +12:01:05.070270 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:01:05.070349 version@stat F·[1 1] S·326KiB[28KiB 297KiB] Sc·[0.25 0.00] +12:01:05.070361 db@open opening +12:01:05.070391 journal@recovery F·1 +12:01:05.070472 journal@recovery recovering @59 +12:01:05.072116 memdb@flush created L0@61 N·1450 S·23KiB "blo..\x01\xc2K,v17337":"blo..\x01\u0082,v18768" +12:01:05.072239 version@stat F·[2 1] S·350KiB[52KiB 297KiB] Sc·[0.50 0.00] +12:01:05.074775 db@janitor F·5 G·0 +12:01:05.074788 db@open done T·4.42315ms +=============== Jun 10, 2024 (UTC) =============== +12:03:00.054328 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:00.054420 version@stat F·[2 1] S·350KiB[52KiB 297KiB] Sc·[0.50 0.00] +12:03:00.054439 db@open opening +12:03:00.054485 journal@recovery F·1 +12:03:00.054723 journal@recovery recovering @62 +12:03:00.056228 memdb@flush created L0@64 N·87 S·1KiB "blo..\x01\xc2},v18788":"blo..\x00\x01\x9f,v18856" +12:03:00.058037 version@stat F·[3 1] S·352KiB[54KiB 297KiB] Sc·[0.75 0.00] +12:03:00.060585 db@janitor F·6 G·0 +12:03:00.060598 db@open done T·6.153555ms +=============== Jun 10, 2024 (UTC) =============== +12:03:19.765209 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:19.765311 version@stat F·[3 1] S·352KiB[54KiB 297KiB] Sc·[0.75 0.00] +12:03:19.765325 db@open opening +12:03:19.765360 journal@recovery F·1 +12:03:19.765469 journal@recovery recovering @65 +12:03:19.766245 memdb@flush created L0@67 N·58 S·1KiB "blo..\x01\u0080,v18876":"blo..\x00\x01\x92,v18915" +12:03:19.766367 version@stat F·[4 1] S·353KiB[55KiB 297KiB] Sc·[1.00 0.00] +12:03:19.768909 db@janitor F·7 G·0 +12:03:19.768920 db@open done T·3.590622ms +12:03:19.768942 table@compaction L0·4 -> L1·1 S·353KiB Q·18934 +12:03:19.777063 table@build created L1@70 N·18914 S·352KiB "\x04\x92\xf2..\v,\x8e,v12512":"\xef\xb9<..\x9a\x1b\x86,v4043" +12:03:19.777089 version@stat F·[0 1] S·352KiB[0B 352KiB] Sc·[0.00 0.00] +12:03:19.777658 table@compaction committed F-4 S-400B Ke·0 D·0 T·8.701467ms +12:03:19.777720 table@remove removed @64 +12:03:19.777758 table@remove removed @61 +12:03:19.777793 table@remove removed @58 +12:03:19.777877 table@remove removed @57 +=============== Jun 10, 2024 (UTC) =============== +12:04:23.857145 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:04:23.857250 version@stat F·[0 1] S·352KiB[0B 352KiB] Sc·[0.00 0.00] +12:04:23.857265 db@open opening +12:04:23.857322 journal@recovery F·1 +12:04:23.857406 journal@recovery recovering @68 +12:04:23.858158 memdb@flush created L0@71 N·29 S·932B "blo..\x01\u0082,v18935":"blo..\x00\x01\x85,v18945" +12:04:23.858466 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:04:23.863301 db@janitor F·5 G·1 +12:04:23.863310 db@janitor removing table-67 +12:04:23.863344 db@open done T·6.073803ms +=============== Jun 10, 2024 (UTC) =============== +12:07:46.940724 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:07:46.940829 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:07:46.940845 db@open opening +12:07:46.940891 journal@recovery F·1 +12:07:46.941015 journal@recovery recovering @72 +12:07:46.941174 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:07:46.944342 db@janitor F·4 G·0 +12:07:46.944357 db@open done T·3.506111ms +=============== Jun 10, 2024 (UTC) =============== +12:08:02.161058 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:08:02.161130 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:08:02.161142 db@open opening +12:08:02.161170 journal@recovery F·1 +12:08:02.161261 journal@recovery recovering @74 +12:08:02.161772 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:08:02.165071 db@janitor F·4 G·0 +12:08:02.165085 db@open done T·3.938975ms +=============== Jun 10, 2024 (UTC) =============== +12:27:21.489214 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:27:21.489293 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:27:21.489306 db@open opening +12:27:21.489335 journal@recovery F·1 +12:27:21.489533 journal@recovery recovering @76 +12:27:21.492693 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:27:21.495334 db@janitor F·4 G·0 +12:27:21.495349 db@open done T·6.038382ms +=============== Jun 10, 2024 (UTC) =============== +12:56:43.860894 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:56:43.860958 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:56:43.860969 db@open opening +12:56:43.861000 journal@recovery F·1 +12:56:43.861094 journal@recovery recovering @78 +12:56:43.861237 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:56:43.864305 db@janitor F·4 G·0 +12:56:43.864316 db@open done T·3.342909ms +=============== Jun 10, 2024 (UTC) =============== +12:57:38.433435 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:57:38.433507 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:57:38.433523 db@open opening +12:57:38.433555 journal@recovery F·1 +12:57:38.435466 journal@recovery recovering @80 +12:57:38.435598 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:57:38.438132 db@janitor F·4 G·0 +12:57:38.438146 db@open done T·4.61711ms +=============== Jun 10, 2024 (UTC) =============== +12:58:01.527259 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:01.527331 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:58:01.527344 db@open opening +12:58:01.527371 journal@recovery F·1 +12:58:01.527448 journal@recovery recovering @82 +12:58:01.528206 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:58:01.530847 db@janitor F·4 G·0 +12:58:01.530860 db@open done T·3.51254ms +=============== Jun 10, 2024 (UTC) =============== +12:58:49.047851 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:49.047923 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:58:49.047936 db@open opening +12:58:49.047971 journal@recovery F·1 +12:58:49.048062 journal@recovery recovering @84 +12:58:49.048387 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:58:49.051827 db@janitor F·4 G·0 +12:58:49.051843 db@open done T·3.900364ms +=============== Jun 10, 2024 (UTC) =============== +12:59:05.188209 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:05.188325 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:59:05.188346 db@open opening +12:59:05.188403 journal@recovery F·1 +12:59:05.188521 journal@recovery recovering @86 +12:59:05.188747 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:59:05.192881 db@janitor F·4 G·0 +12:59:05.192895 db@open done T·4.542609ms +=============== Jun 10, 2024 (UTC) =============== +12:59:32.467734 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:32.467839 version@stat F·[1 1] S·353KiB[932B 352KiB] Sc·[0.25 0.00] +12:59:32.467852 db@open opening +12:59:32.467882 journal@recovery F·1 +12:59:32.468080 journal@recovery recovering @88 +12:59:32.469891 memdb@flush created L0@90 N·29 S·925B "blo..\x01\u0083,v18965":"blo..\x00\x01\x85,v18975" +12:59:32.472164 version@stat F·[2 1] S·354KiB[1KiB 352KiB] Sc·[0.50 0.00] +12:59:32.475396 db@janitor F·5 G·0 +12:59:32.475411 db@open done T·7.555145ms +=============== Jul 3, 2024 (UTC) =============== +17:52:58.396919 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:52:58.396991 version@stat F·[2 1] S·354KiB[1KiB 352KiB] Sc·[0.50 0.00] +17:52:58.397003 db@open opening +17:52:58.397031 journal@recovery F·1 +17:52:58.397205 journal@recovery recovering @91 +17:52:58.400312 version@stat F·[2 1] S·354KiB[1KiB 352KiB] Sc·[0.50 0.00] +17:52:58.402997 db@janitor F·5 G·0 +17:52:58.403009 db@open done T·6.0015ms +=============== Jul 3, 2024 (UTC) =============== +17:59:01.767011 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:59:01.767114 version@stat F·[2 1] S·354KiB[1KiB 352KiB] Sc·[0.50 0.00] +17:59:01.767128 db@open opening +17:59:01.767161 journal@recovery F·1 +17:59:01.767237 journal@recovery recovering @93 +17:59:01.769440 memdb@flush created L0@95 N·2140 S·59KiB "\x12\xf5\xa5..b\x01[,v20843":"\xe0\x1e\x98..\x9a\x10\x81,v20696" +17:59:01.769561 version@stat F·[3 1] S·414KiB[61KiB 352KiB] Sc·[0.75 0.00] +17:59:01.773003 db@janitor F·6 G·0 +17:59:01.773016 db@open done T·5.882999ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.924218 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.924296 version@stat F·[3 1] S·414KiB[61KiB 352KiB] Sc·[0.75 0.00] +05:10:40.924308 db@open opening +05:10:40.924344 journal@recovery F·1 +05:10:40.924448 journal@recovery recovering @96 +05:10:40.925246 memdb@flush created L0@98 N·145 S·2KiB "blo..\x01\xc2\xc9,v21136":"blo..\x00\x01\xb9,v21262" +05:10:40.925383 version@stat F·[4 1] S·417KiB[64KiB 352KiB] Sc·[1.00 0.00] +05:10:40.928590 db@janitor F·7 G·0 +05:10:40.928600 db@open done T·4.288823ms +05:10:40.928646 table@compaction L0·4 -> L1·1 S·417KiB Q·21281 +05:10:40.938446 table@build created L1@101 N·21257 S·415KiB "\x04\x92\xf2..\v,\x8e,v12512":"\xef\xb9<..\x9a\x1b\x86,v4043" +05:10:40.938478 version@stat F·[0 1] S·415KiB[0B 415KiB] Sc·[0.00 0.00] +05:10:40.939072 table@compaction committed F-4 S-1KiB Ke·0 D·0 T·10.406849ms +05:10:40.939148 table@remove removed @95 +05:10:40.939176 table@remove removed @90 +05:10:40.939200 table@remove removed @71 +05:10:40.939286 table@remove removed @70 +=============== Oct 17, 2024 (UTC) =============== +06:44:58.713909 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.714036 version@stat F·[0 1] S·415KiB[0B 415KiB] Sc·[0.00 0.00] +06:44:58.714046 db@open opening +06:44:58.714080 journal@recovery F·1 +06:44:58.714171 journal@recovery recovering @99 +06:44:58.717466 memdb@flush created L0@102 N·4372 S·103KiB "!\xe6\x14..CN\x88,v25566":"\xf8@\xbc..Ï\xfe,v23666" +06:44:58.717707 version@stat F·[1 1] S·518KiB[103KiB 415KiB] Sc·[0.25 0.00] +06:44:58.730090 db@janitor F·5 G·1 +06:44:58.730096 db@janitor removing table-98 +06:44:58.730122 db@open done T·16.072878ms +=============== Oct 17, 2024 (UTC) =============== +06:45:46.648667 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.648738 version@stat F·[1 1] S·518KiB[103KiB 415KiB] Sc·[0.25 0.00] +06:45:46.648749 db@open opening +06:45:46.648777 journal@recovery F·1 +06:45:46.650719 journal@recovery recovering @103 +06:45:46.651755 memdb@flush created L0@105 N·264 S·12KiB "blo..\x01\xc3^,v25655":"\xa0i\xe8..f\x92\x12,v25726" +06:45:46.653729 version@stat F·[2 1] S·531KiB[116KiB 415KiB] Sc·[0.50 0.00] +06:45:46.656293 db@janitor F·5 G·0 +06:45:46.656305 db@open done T·7.551795ms +=============== Oct 17, 2024 (UTC) =============== +06:47:26.274594 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.274675 version@stat F·[2 1] S·531KiB[116KiB 415KiB] Sc·[0.50 0.00] +06:47:26.274686 db@open opening +06:47:26.274721 journal@recovery F·1 +06:47:26.276698 journal@recovery recovering @106 +06:47:26.277708 memdb@flush created L0@108 N·435 S·7KiB "blo..\x01\xc3e,v25920":"blo..\x01\xc0\xbb,v26336" +06:47:26.279711 version@stat F·[3 1] S·538KiB[123KiB 415KiB] Sc·[0.75 0.00] +06:47:26.282380 db@janitor F·6 G·0 +06:47:26.282394 db@open done T·7.703855ms diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/MANIFEST-000110 b/.docker/container-state/atom-testnet-data/data/tx_index.db/MANIFEST-000110 new file mode 100644 index 0000000000..422b4dc63e Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/tx_index.db/MANIFEST-000110 differ diff --git a/.docker/container-state/atom-testnet-data/data/write-file-atomic-03223747255274497650 b/.docker/container-state/atom-testnet-data/data/write-file-atomic-03223747255274497650 new file mode 100644 index 0000000000..bc534f0b06 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/data/write-file-atomic-03223747255274497650 @@ -0,0 +1,7 @@ +{ + "height": "343", + "round": 0, + "step": 3, + "signature": "MY8ah2Oe2uLdMiP3JJQ++r2nUP3Nf3E/SdU0oumWpOFQpsLgFnJusiKgbbuUq1vlfEzZvNzC+Iq1Q4x6Ma80Bw==", + "signbytes": "76080211570100000000000022480A202E5A85F34706FA52E68449B0D7C4096A8AA01C74C0F4F774E793501209B7D13A122408011220C688A67C51EDAFDCF4036BB86B89E5916C3A84D5FBACB7D32BD011EB31F597312A0C08F2A99BB306109884F3A2023211636F736D6F736875622D746573746E6574" +} \ No newline at end of file diff --git a/.docker/container-state/atom-testnet-data/keyring-test/224884bc43a4345c66051befe55c0c54676008b3.address b/.docker/container-state/atom-testnet-data/keyring-test/224884bc43a4345c66051befe55c0c54676008b3.address new file mode 100644 index 0000000000..80ea7eb88a --- /dev/null +++ b/.docker/container-state/atom-testnet-data/keyring-test/224884bc43a4345c66051befe55c0c54676008b3.address @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wNi0wMyAxNDo0MzozMy40NzUxMDYyMzIgKzAzMDAgKzAzIG09KzAuMDUwMjQzNDYzIiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoiNDVFd1ZvLXBsUTg5TWhwbiJ9.D2_MVk-uV5oSHZ_1fTMPaInNKJYV8mq4j4vLRDpXpcc6Y64UeMhTrg.8ObGr4ch02Ueij_w._dys4Xkk15-eBFn0Hp1ND1D5a6x1qeiEZd4nDk9zH7nYX8pHVTI0aMf-eMBPwyrvNlP4HR7kP32aMfzOG5-KGfjVTMvFU1uzvddktQMAADiDII_anlp9SHFQ2OE5ElafEb0TrM42_nKO52NIjBoR11BnrlklbNSe7Z_5_aJFtkvPeI3M6JMgL8-3HvlrVxh1jONNTfzI6BsMRu9oyz7czzjt_CCBykZiB3MORpZXUiAdvQ.fXxi52nksYKozG5ZgB-I8w \ No newline at end of file diff --git a/.docker/container-state/atom-testnet-data/keyring-test/a36b04d45a23d962b67cd9c82b3065c45c0a80e5.address b/.docker/container-state/atom-testnet-data/keyring-test/a36b04d45a23d962b67cd9c82b3065c45c0a80e5.address new file mode 100644 index 0000000000..f973909eb2 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/keyring-test/a36b04d45a23d962b67cd9c82b3065c45c0a80e5.address @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wNi0wMyAxNDo0MzozMy40MTYyMDA1NjkgKzAzMDAgKzAzIG09KzAuMDUwODg5Njg4IiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoiYXQ4QWxwUzVuVWFGWlZRUSJ9.X2pj9isOPsF7cKkoyVas6VcJB-m_AAxbA701Kmh3Yk7VwaxdQ5Qo2g.vI2ySGJ5ddMTDiHb.zahJ5cHOvxC54v4JT96sst89jXwIdMIfgsGQpxLyNMRthst7imfGyxy2TJALHbZnZ8kHSkKzV5iCCmc2PseV1QDd9uYzzqPyaNj_4521XaublQSQVwKx7dWx_ZO_m4L4TohqiH9RGXgWWXULmPDpbPeHR-IQCXQQCpcCt-xgYnu2NQlpsHjVJEu6GnHadSBGTQSpmlJOEBWBrCo3GWMDh2BZNmGZtpHjetCFjvUtWQydRFkO6M4.LGozg-bJyZPFpLp6GSFo1g \ No newline at end of file diff --git a/.docker/container-state/atom-testnet-data/keyring-test/alice.info b/.docker/container-state/atom-testnet-data/keyring-test/alice.info new file mode 100644 index 0000000000..6190860f95 --- /dev/null +++ b/.docker/container-state/atom-testnet-data/keyring-test/alice.info @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wNi0wMyAxNDo0MzozMy40MTQzMjMyMjQgKzAzMDAgKzAzIG09KzAuMDQ5MDEyMzUzIiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoiVUtiMERCV3QxSUdVM1BVcSJ9.3PC7McaHjy5hj0TMb0_ehGBkaet2VOJv-ct2O3sQu1X-tG70EozO5Q.dhHoSV0VHixAut1x.51GHbUPRH0hP7gwT5Cn4wjSIQ6QkeeR-E5Wt5E9H3oalsPj71PeZBr_xcLVs76RYcHo_9EgDZWVROMMGqNPW_3lvOIGcSdL3xY-XWFvC-Lk80PwQTlG-IVrlVjzfYX1q4iCSA1IVZ_5N6G-7Ru0vWsLrkzjm4A_0PbFKLnyIDxEvm1hyvt36EdpALB1OjHApG6WdL6XGaOw-ENNngGgaRuB0zxXpaFtoEaUargiSc9L-0wxODppRVbeHxbprNbQ3LTD2YT_gL-ckLQ2GWn83I4NeAGZNQLdBtqV_FXYecsUVRTsBT5x_GZA_eI2VxPUda6q-4pNdnK2F2cz7z2INJUXZkvXJLGAMWW71uzi4VOFSKvHWorAC_dGBhdzduOI2z-FyI4u_r6mgshHzG98ustvS0V5r9v6_REdPPVlbrB61sg-tSEzOFvGSDKE.SC8KcfH62fAttZk07w1PKg \ No newline at end of file diff --git a/.docker/container-state/atom-testnet-data/keyring-test/bob.info b/.docker/container-state/atom-testnet-data/keyring-test/bob.info new file mode 100644 index 0000000000..8791ab5e6c --- /dev/null +++ b/.docker/container-state/atom-testnet-data/keyring-test/bob.info @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wNi0wMyAxNDo0MzozMy40NzMyNDIwNTcgKzAzMDAgKzAzIG09KzAuMDQ4Mzc5Mjk4IiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoiMXdoTEJ3Z1dJMGJNWmk3ZSJ9.AEebIffKKSlCliXJlq3ges1jhA5W-oSThTZRBXwR79ZQX_CkrQM_xg.pp8DeTHU5c_aDLe8.3y585dIrkDKD9NwhjfoQ3f_Wxt3VyZXgn-bS_FBbkHPyUi64yrSOLpxMZy8YWIUHLtJntofm0bgOfHvouwu2nwG73_mUJTB0EXNIBg0ynK_7dlkf_dlVWxX-dfzXnpNpvD94FJLfRbMeNLMWzdIpp7ggq-j2CbudBIjHTTXDv_nNi9Xy4kqu6endAy1XfThCR2KYaTNzBzBnad0RkT_rIsa7tOgo_huYyeik0ZujVt9__jEYjTgyML9fL-c_bAHdaqCEhvWQKxEYyfIZFm_rQFQgqVR0UYWGDfRdYRechQxE3CHOGQ-O-FnSp-ZQzoIq_t2X7bS4qG6nciADdfLtNXdCyUKjujp5zYx5NcoE2YYi6qacrjozsLIXWPlP0jf2Gx7pn1ljfOd7Pzs1O8c58UBQXsjNvtcqWKgnFmz6JEI9uuoVtGsAQz66.YDdLNtYlQPh8lNC7GfN82g \ No newline at end of file diff --git a/.docker/container-state/ibc-relayer-data/config/config.lock b/.docker/container-state/ibc-relayer-data/config/config.lock new file mode 100755 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/ibc-relayer-data/config/config.yaml b/.docker/container-state/ibc-relayer-data/config/config.yaml new file mode 100755 index 0000000000..77886d2f40 --- /dev/null +++ b/.docker/container-state/ibc-relayer-data/config/config.yaml @@ -0,0 +1,74 @@ +global: + api-listen-addr: :5183 + timeout: 10s + memo: "" + light-cache-size: 20 + log-level: "" + ics20-memo-limit: 0 + max-receiver-size: 0 +chains: + atom: + type: cosmos + value: + key-directory: /root/.relayer/keys/cosmoshub-testnet + key: test2 + chain-id: cosmoshub-testnet + rpc-addr: http://127.0.0.1:26658 + account-prefix: cosmos + keyring-backend: test + dynamic-gas-price: false + gas-adjustment: 1.8 + gas-prices: 0.5uatom + min-gas-amount: 0 + max-gas-amount: 0 + debug: true + timeout: 20s + block-timeout: "" + output-format: json + sign-mode: direct + extra-codecs: [] + coin-type: null + signing-algorithm: "" + broadcast-mode: batch + min-loop-duration: 0s + extension-options: [] + feegrants: null + nucleus: + type: cosmos + value: + key-directory: /root/.relayer/keys/nucleus-testnet + key: test1 + chain-id: nucleus-testnet + rpc-addr: http://127.0.0.1:26657 + account-prefix: nuc + keyring-backend: test + dynamic-gas-price: false + gas-adjustment: 1.8 + gas-prices: 0.5unucl + min-gas-amount: 0 + max-gas-amount: 0 + debug: true + timeout: 20s + block-timeout: "" + output-format: json + sign-mode: direct + extra-codecs: [] + coin-type: null + signing-algorithm: "" + broadcast-mode: batch + min-loop-duration: 0s + extension-options: [] + feegrants: null +paths: + nucleus-atom: + src: + chain-id: nucleus-testnet + client-id: 07-tendermint-2 + connection-id: connection-2 + dst: + chain-id: cosmoshub-testnet + client-id: 07-tendermint-2 + connection-id: connection-2 + src-channel-filter: + rule: "" + channel-list: [] diff --git a/.docker/container-state/ibc-relayer-data/keys/cosmoshub-testnet/keyring-test/b93a76d5dd5607a7512be7209a8fa9470038a1c1.address b/.docker/container-state/ibc-relayer-data/keys/cosmoshub-testnet/keyring-test/b93a76d5dd5607a7512be7209a8fa9470038a1c1.address new file mode 100755 index 0000000000..180a543e54 --- /dev/null +++ b/.docker/container-state/ibc-relayer-data/keys/cosmoshub-testnet/keyring-test/b93a76d5dd5607a7512be7209a8fa9470038a1c1.address @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wNi0xMCAxMzoxODoyNC40OTM4ODA1NDcgKzAzMDAgKzAzIG09KzAuMDQzODQ1NDYwIiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoiOUZyZVdPcTktbUdqejR3TSJ9.quAlH3Hr-wRWG4Njljd5_2NxlKUjjUBbxSbyolHvb6lj7de9zbiO3w.RWefar5xuM4KP6Hy.1lXXgeQih0ibaSNmXUWgz08L7v9ASYaIfkCeQgqlyO7UPnI6QvymXE44npML0tzjCLSvMuFGtSALgdQYVLr2NdbSDJ0CTJI0Eo26ll5H6t6IFWB4vhSENAg_5EvFgzIyllR7cVvMMjdt_O4QpjZG9CZIGxGZLBF1FdDNQke-vX5HbZOyf-zq9LDb-79bKj4tt1M8HaFw4LX1-UY503XzGHauFW4JoZx2PzNHkwCBhK4q5ygYT-8.7qJKM-Kvhl3jPk9QpE3FUQ \ No newline at end of file diff --git a/.docker/container-state/ibc-relayer-data/keys/cosmoshub-testnet/keyring-test/test2.info b/.docker/container-state/ibc-relayer-data/keys/cosmoshub-testnet/keyring-test/test2.info new file mode 100755 index 0000000000..1164b8c6a6 --- /dev/null +++ b/.docker/container-state/ibc-relayer-data/keys/cosmoshub-testnet/keyring-test/test2.info @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wNi0xMCAxMzoxODoyNC40OTAxNDcyMzUgKzAzMDAgKzAzIG09KzAuMDQwMTEyMTU4IiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoiZmJvNnpfaWpxc3loX2VOcyJ9.TvymOktT7IZ1BsPqGiIsi6Fn6be1jZL8hqw46fph1j2NhxP_1yq0cA.XLrFWk8BXL_WZ2aA.01hpPXfBtMMmjwdJgSprKCo_bFDTaRsZ5j9jxF5vVjDYKHjE0D2JFkDlhFaxNujvoEfnvreNTxhAHs9N7zKgVPlTXRK-PK0SoMeg3EBsmaPf4VIbpdQDXNtjP9MZmeta7KZ7Fur3kSGAmh8vGaVkKlep-3JCYNAbsXyympfpw5bgZNSjn380QEQ3C_SoyupcRm3Xb6NZgGYGPlfXDHdzEbao0pMopZN-OZFSobOEzdQv3CCNdvQa9vsO3lpyWKlwB7S3CxQDvTuWwnFIDA-p7Y19QoXp_i17lYRc73CDGubR8EFgHXfQub4du6RUZzRWhn8mThIZ-EqrWbN1YPmWNMlCiYSozhakAfpdc6v34ksTZjTEd5rSG7hQCfOaosK2XASnsAD9KtzOAs1UrYschCoSYRrDEgqv4miGrL-Vm0myNBVpP7tTEU5VkIU.Ko-RDxf7wgzDSz2tf5N67g \ No newline at end of file diff --git a/.docker/container-state/ibc-relayer-data/keys/nucleus-testnet/keyring-test/528769d0cfb20433088d2112cfce79ac4123f0c5.address b/.docker/container-state/ibc-relayer-data/keys/nucleus-testnet/keyring-test/528769d0cfb20433088d2112cfce79ac4123f0c5.address new file mode 100755 index 0000000000..bf29dd8d61 --- /dev/null +++ b/.docker/container-state/ibc-relayer-data/keys/nucleus-testnet/keyring-test/528769d0cfb20433088d2112cfce79ac4123f0c5.address @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wNi0xMCAxMzoxODozMy41NjIyOTY1NDUgKzAzMDAgKzAzIG09KzAuMDQzODYyMzgwIiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoiNUc3M3FJUFR4T29sWmZiMyJ9.-NzHhAimX8YJmZ62HjfwZ6HObP76uh9Li8OgK7CD63XTZLsktWRRcg.887WuM1rWe5dtD-3.kcUfpyEa6T3KhffcEThO5VHV_lOn7KL4HrVJV5gLX3bTS3nRH2eS4y0bo9ZBsXkuUjiPv7kAcFWTDbeGFn12C70Gd81YlBjT2lra-xsOS64n_BBgMYuk38BnAg5kFgQ5iqq4GCBtOQtYNoLx07AHXID3Wc3gXtZp_OB3JdfemibQMQFC8t2WQH3M0ndZCnM3GaqzdwZt_N1X4TE5luyNq-3llRTxg2WuTmhGQre90ygUIVocuRw.2WX1GaPqNiGxZSPqNfRIog \ No newline at end of file diff --git a/.docker/container-state/ibc-relayer-data/keys/nucleus-testnet/keyring-test/test1.info b/.docker/container-state/ibc-relayer-data/keys/nucleus-testnet/keyring-test/test1.info new file mode 100755 index 0000000000..84af982a42 --- /dev/null +++ b/.docker/container-state/ibc-relayer-data/keys/nucleus-testnet/keyring-test/test1.info @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wNi0xMCAxMzoxODozMy41NTg2NDU1NzQgKzAzMDAgKzAzIG09KzAuMDQwMjExNDA5IiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoienJNclpwdUhJM1VTd21pZCJ9.Xp7j59CIMe3Ytjudw5fejiLzSbMSPppSrjnP-zci5CNlgQ0wkHmfhQ.B4tCZcVPF0lfEFsw.bHIRYqPYroRbvOqz5gac7w3K5M-uORPQvgyE6GF5c_-Nxu2OhCRr7anGVPxmYp3p3whOp5ky9GoVgnle7a4nUR9JPkgv94xI-bYgTZ7kTipuvom20vsL1zc-xqGSYLjw8skhVMZ0thkOQa8spuqMXtB3YJdPF43rA0U2DBGq8rCvoy8mDdLfuYCqJXSULKxjIlMK8zRFGp5RDM3IXknrsUk-yyfR5Bs9WUE0R3hrsnL0SU4r6QjhkpKrvBziKPYVrgul9bix68x1ZC8Pb0bZObhhmCw2CPH_wHscVT6hsBDzRmpsU4OvIYZJbMmRPpYm_ylTB4TInhZGlmOwJXk_Yv0X_ojlXxjB08prJKgufxSkAG8tkvmQW_bLmDDxPnRvH_q9mHEpwBGLP4es3_sAu8mEtb-uwwZlx4DWY_tepJcXOLDDeTp1J79GZWg.RkIc0UsZDYMh3AexHloGMA \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/addrbook.json b/.docker/container-state/nucleus-testnet-data/config/addrbook.json new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/addrbook.json @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/app.toml b/.docker/container-state/nucleus-testnet-data/config/app.toml new file mode 100644 index 0000000000..a01507cd51 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/app.toml @@ -0,0 +1,245 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +############################################################################### +### Base Configuration ### +############################################################################### + +# The minimum gas prices a validator is willing to accept for processing a +# transaction. A transaction's fees must meet the minimum of any denomination +# specified in this config (e.g. 0.25token1;0.0001token2). +minimum-gas-prices = "0.0025unucl" + +# default: the last 362880 states are kept, pruning at 10 block intervals +# nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node) +# everything: 2 latest states will be kept; pruning at 10 block intervals. +# custom: allow pruning options to be manually specified through 'pruning-keep-recent', and 'pruning-interval' +pruning = "default" + +# These are applied if and only if the pruning strategy is custom. +pruning-keep-recent = "0" +pruning-interval = "0" + +# HaltHeight contains a non-zero block height at which a node will gracefully +# halt and shutdown that can be used to assist upgrades and testing. +# +# Note: Commitment of state will be attempted on the corresponding block. +halt-height = 0 + +# HaltTime contains a non-zero minimum block time (in Unix seconds) at which +# a node will gracefully halt and shutdown that can be used to assist upgrades +# and testing. +# +# Note: Commitment of state will be attempted on the corresponding block. +halt-time = 0 + +# MinRetainBlocks defines the minimum block height offset from the current +# block being committed, such that all blocks past this offset are pruned +# from Tendermint. It is used as part of the process of determining the +# ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates +# that no blocks should be pruned. +# +# This configuration value is only responsible for pruning Tendermint blocks. +# It has no bearing on application state pruning which is determined by the +# "pruning-*" configurations. +# +# Note: Tendermint block pruning is dependant on this parameter in conunction +# with the unbonding (safety threshold) period, state pruning and state sync +# snapshot parameters to determine the correct minimum value of +# ResponseCommit.RetainHeight. +min-retain-blocks = 0 + +# InterBlockCache enables inter-block caching. +inter-block-cache = true + +# IndexEvents defines the set of events in the form {eventType}.{attributeKey}, +# which informs Tendermint what to index. If empty, all events will be indexed. +# +# Example: +# ["message.sender", "message.recipient"] +index-events = [] + +# IavlCacheSize set the size of the iavl tree cache. +# Default cache size is 50mb. +iavl-cache-size = 781250 + +# IavlDisableFastNode enables or disables the fast node feature of IAVL. +# Default is false. +iavl-disable-fastnode = false + +# AppDBBackend defines the database backend type to use for the application and snapshots DBs. +# An empty string indicates that a fallback will be used. +# First fallback is the deprecated compile-time types.DBBackend value. +# Second fallback (if the types.DBBackend also isn't set), is the db-backend value set in Tendermint's config.toml. +app-db-backend = "" + +############################################################################### +### Telemetry Configuration ### +############################################################################### + +[telemetry] + +# Prefixed with keys to separate services. +service-name = "" + +# Enabled enables the application telemetry functionality. When enabled, +# an in-memory sink is also enabled by default. Operators may also enabled +# other sinks such as Prometheus. +enabled = false + +# Enable prefixing gauge values with hostname. +enable-hostname = false + +# Enable adding hostname to labels. +enable-hostname-label = false + +# Enable adding service to labels. +enable-service-label = false + +# PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. +prometheus-retention-time = 0 + +# GlobalLabels defines a global set of name/value label tuples applied to all +# metrics emitted using the wrapper functions defined in telemetry package. +# +# Example: +# [["chain_id", "cosmoshub-1"]] +global-labels = [ +] + +############################################################################### +### API Configuration ### +############################################################################### + +[api] + +# Enable defines if the API server should be enabled. +enable = true + +# Swagger defines if swagger documentation should automatically be registered. +swagger = true + +# Address defines the API server to listen on. +address = "tcp://0.0.0.0:1317" + +# MaxOpenConnections defines the number of maximum open connections. +max-open-connections = 1000 + +# RPCReadTimeout defines the Tendermint RPC read timeout (in seconds). +rpc-read-timeout = 10 + +# RPCWriteTimeout defines the Tendermint RPC write timeout (in seconds). +rpc-write-timeout = 0 + +# RPCMaxBodyBytes defines the Tendermint maximum response body (in bytes). +rpc-max-body-bytes = 1000000 + +# EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk). +enabled-unsafe-cors = true + +############################################################################### +### Rosetta Configuration ### +############################################################################### + +[rosetta] + +# Enable defines if the Rosetta API server should be enabled. +enable = false + +# Address defines the Rosetta API server to listen on. +address = ":8080" + +# Network defines the name of the blockchain that will be returned by Rosetta. +blockchain = "app" + +# Network defines the name of the network that will be returned by Rosetta. +network = "network" + +# Retries defines the number of retries when connecting to the node before failing. +retries = 3 + +# Offline defines if Rosetta server should run in offline mode. +offline = false + +# EnableDefaultSuggestedFee defines if the server should suggest fee by default. +# If 'construction/medata' is called without gas limit and gas price, +# suggested fee based on gas-to-suggest and denom-to-suggest will be given. +enable-fee-suggestion = false + +# GasToSuggest defines gas limit when calculating the fee +gas-to-suggest = 200000 + +# DenomToSuggest defines the defult denom for fee suggestion. +# Price must be in minimum-gas-prices. +denom-to-suggest = "unucl" + +############################################################################### +### gRPC Configuration ### +############################################################################### + +[grpc] + +# Enable defines if the gRPC server should be enabled. +enable = true + +# Address defines the gRPC server address to bind to. +address = "0.0.0.0:9090" + +# MaxRecvMsgSize defines the max message size in bytes the server can receive. +# The default value is 10MB. +max-recv-msg-size = "10485760" + +# MaxSendMsgSize defines the max message size in bytes the server can send. +# The default value is math.MaxInt32. +max-send-msg-size = "2147483647" + +############################################################################### +### gRPC Web Configuration ### +############################################################################### + +[grpc-web] + +# GRPCWebEnable defines if the gRPC-web should be enabled. +# NOTE: gRPC must also be enabled, otherwise, this configuration is a no-op. +enable = true + +# Address defines the gRPC-web server address to bind to. +address = "0.0.0.0:9091" + +# EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk). +enable-unsafe-cors = true + +############################################################################### +### State Sync Configuration ### +############################################################################### + +# State sync snapshots allow other nodes to rapidly join the network without replaying historical +# blocks, instead downloading and applying a snapshot of the application state at a given height. +[state-sync] + +# snapshot-interval specifies the block interval at which local state sync snapshots are +# taken (0 to disable). +snapshot-interval = 0 + +# snapshot-keep-recent specifies the number of recent snapshots to keep and serve (0 to keep all). +snapshot-keep-recent = 2 + +############################################################################### +### Store / State Streaming ### +############################################################################### + +[store] +streamers = [] + +[streamers] +[streamers.file] +keys = ["*", ] +write_dir = "" +prefix = "" + +[wasm] +# This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries +query_gas_limit = 300000 +# This is the number of wasm vm instances we keep cached in memory for speed-up +# Warning: this is currently unstable and may lead to crashes, best to keep for 0 unless testing locally +lru_size = 0 diff --git a/.docker/container-state/nucleus-testnet-data/config/client.toml b/.docker/container-state/nucleus-testnet-data/config/client.toml new file mode 100644 index 0000000000..4de71d43a6 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/client.toml @@ -0,0 +1,17 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +############################################################################### +### Client Configuration ### +############################################################################### + +# The network chain ID +chain-id = "nucleus-testnet" +# The keyring's backend, where the keys are stored (os|file|kwallet|pass|test|memory) +keyring-backend = "test" +# CLI output format (text|json) +output = "text" +# : to Tendermint RPC interface for this chain +node = "tcp://localhost:26657" +# Transaction broadcasting mode (sync|async|block) +broadcast-mode = "sync" diff --git a/.docker/container-state/nucleus-testnet-data/config/config.toml b/.docker/container-state/nucleus-testnet-data/config/config.toml new file mode 100644 index 0000000000..8292219e1f --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/config.toml @@ -0,0 +1,466 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +# NOTE: Any path below can be absolute (e.g. "/var/myawesomeapp/data") or +# relative to the home directory (e.g. "data"). The home directory is +# "$HOME/.tendermint" by default, but could be changed via $TMHOME env variable +# or --home cmd flag. + +####################################################################### +### Main Base Config Options ### +####################################################################### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "nimda" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb +# * goleveldb (github.com/syndtr/goleveldb - most popular implementation) +# - pure go +# - stable +# * cleveldb (uses levigo wrapper) +# - fast +# - requires gcc +# - use cleveldb build tag (go build -tags cleveldb) +# * boltdb (uses etcd's fork of bolt - github.com/etcd-io/bbolt) +# - EXPERIMENTAL +# - may be faster is some use-cases (random reads - indexer) +# - use boltdb build tag (go build -tags boltdb) +# * rocksdb (uses github.com/tecbot/gorocksdb) +# - EXPERIMENTAL +# - requires gcc +# - use rocksdb build tag (go build -tags rocksdb) +# * badgerdb (uses github.com/dgraph-io/badger) +# - EXPERIMENTAL +# - use badgerdb build tag (go build -tags badgerdb) +db_backend = "goleveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "info" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_key_file = "config/priv_validator_key.json" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "data/priv_validator_state.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + + +####################################################################### +### Advanced Configuration Options ### +####################################################################### + +####################################################### +### RPC Server Configuration Options ### +####################################################### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://0.0.0.0:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +# Maximum number of unique clientIDs that can /subscribe +# If you're using /broadcast_tx_commit, set to the estimated maximum number +# of broadcast_tx_commit calls per block. +max_subscription_clients = 100 + +# Maximum number of unique queries a given client can /subscribe to +# If you're using GRPC (or Local RPC client) and /broadcast_tx_commit, set to +# the estimated # maximum number of broadcast_tx_commit calls per block. +max_subscriptions_per_client = 5 + +# Experimental parameter to specify the maximum number of events a node will +# buffer, per subscription, before returning an error and closing the +# subscription. Must be set to at least 100, but higher values will accommodate +# higher event throughput rates (and will use more memory). +experimental_subscription_buffer_size = 200 + +# Experimental parameter to specify the maximum number of RPC responses that +# can be buffered per WebSocket client. If clients cannot read from the +# WebSocket endpoint fast enough, they will be disconnected, so increasing this +# parameter may reduce the chances of them being disconnected (but will cause +# the node to use more memory). +# +# Must be at least the same as "experimental_subscription_buffer_size", +# otherwise connections could be dropped unnecessarily. This value should +# ideally be somewhat higher than "experimental_subscription_buffer_size" to +# accommodate non-subscription-related RPC responses. +experimental_websocket_write_buffer_size = 200 + +# If a WebSocket client cannot read fast enough, at present we may +# silently drop events instead of generating an error or disconnecting the +# client. +# +# Enabling this experimental parameter will cause the WebSocket connection to +# be closed instead if it cannot read fast enough, allowing for greater +# predictability in subscription behaviour. +experimental_close_on_slow_client = false + +# How long to wait for a tx to be committed during /broadcast_tx_commit. +# WARNING: Using a value larger than 10s will result in increasing the +# global HTTP write timeout, which applies to all connections and endpoints. +# See https://github.com/tendermint/tendermint/issues/3435 +timeout_broadcast_tx_commit = "10s" + +# Maximum size of request body, in bytes +max_body_bytes = 1000000 + +# Maximum size of request header, in bytes +max_header_bytes = 1048576 + +# The path to a file containing certificate that is used to create the HTTPS server. +# Might be either absolute path or path related to Tendermint's config directory. +# If the certificate is signed by a certificate authority, +# the certFile should be the concatenation of the server's certificate, any intermediates, +# and the CA's certificate. +# NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. +# Otherwise, HTTP server is run. +tls_cert_file = "" + +# The path to a file containing matching private key that is used to create the HTTPS server. +# Might be either absolute path or path related to Tendermint's config directory. +# NOTE: both tls-cert-file and tls-key-file must be present for Tendermint to create HTTPS server. +# Otherwise, HTTP server is run. +tls_key_file = "" + +# pprof listen address (https://golang.org/pkg/net/http/pprof) +pprof_laddr = "localhost:6060" + +####################################################### +### P2P Configuration Options ### +####################################################### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. ip and port are required +# example: 159.89.10.97:26656 +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# List of node IDs, to which a connection will be (re)established ignoring any existing limits +unconditional_peer_ids = "" + +# Maximum pause when redialing a persistent peer (if zero, exponential backoff is used) +persistent_peers_max_dial_period = "0s" + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = false + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +####################################################### +### Mempool Configuration Option ### +####################################################### +[mempool] + +# Mempool version to use: +# 1) "v0" - (default) FIFO mempool. +# 2) "v1" - prioritized mempool. +version = "v0" + +recheck = true +broadcast = true +wal_dir = "" + +# Maximum number of transactions in the mempool +size = 5000 + +# Limit the total size of all txs in the mempool. +# This only accounts for raw transactions (e.g. given 1MB transactions and +# max_txs_bytes=5MB, mempool will only accept 5 transactions). +max_txs_bytes = 1073741824 + +# Size of the cache (used to filter transactions we saw earlier) in transactions +cache_size = 10000 + +# Do not remove invalid transactions from the cache (default: false) +# Set to true if it's not possible for any invalid transaction to become valid +# again in the future. +keep-invalid-txs-in-cache = false + +# Maximum size of a single transaction. +# NOTE: the max size of a tx transmitted over the network is {max_tx_bytes}. +max_tx_bytes = 1048576 + +# Maximum size of a batch of transactions to send to a peer +# Including space needed by encoding (one varint per transaction). +# XXX: Unused due to https://github.com/tendermint/tendermint/issues/5796 +max_batch_bytes = 0 + +# ttl-duration, if non-zero, defines the maximum amount of time a transaction +# can exist for in the mempool. +# +# Note, if ttl-num-blocks is also defined, a transaction will be removed if it +# has existed in the mempool at least ttl-num-blocks number of blocks or if it's +# insertion time into the mempool is beyond ttl-duration. +ttl-duration = "0s" + +# ttl-num-blocks, if non-zero, defines the maximum number of blocks a transaction +# can exist for in the mempool. +# +# Note, if ttl-duration is also defined, a transaction will be removed if it +# has existed in the mempool at least ttl-num-blocks number of blocks or if +# it's insertion time into the mempool is beyond ttl-duration. +ttl-num-blocks = 0 + +####################################################### +### State Sync Configuration Options ### +####################################################### +[statesync] +# State sync rapidly bootstraps a new node by discovering, fetching, and restoring a state machine +# snapshot from peers instead of fetching and replaying historical blocks. Requires some peers in +# the network to take and serve state machine snapshots. State sync is not attempted if the node +# has any local state (LastBlockHeight > 0). The node will have a truncated block history, +# starting from the height of the snapshot. +enable = false + +# RPC servers (comma-separated) for light client verification of the synced state machine and +# retrieval of state data for node bootstrapping. Also needs a trusted height and corresponding +# header hash obtained from a trusted source, and a period during which validators can be trusted. +# +# For Cosmos SDK-based chains, trust_period should usually be about 2/3 of the unbonding time (~2 +# weeks) during which they can be financially punished (slashed) for misbehavior. +rpc_servers = "" +trust_height = 0 +trust_hash = "" +trust_period = "168h0m0s" + +# Time to spend discovering snapshots before initiating a restore. +discovery_time = "15s" + +# Temporary directory for state sync snapshot chunks, defaults to the OS tempdir (typically /tmp). +# Will create a new, randomly named directory within, and remove it when done. +temp_dir = "" + +# The timeout duration before re-requesting a chunk, possibly from a different +# peer (default: 1 minute). +chunk_request_timeout = "10s" + +# The number of concurrent chunk fetchers to run (default: 1). +chunk_fetchers = "4" + +####################################################### +### Fast Sync Configuration Connections ### +####################################################### +[fastsync] + +# Fast Sync version to use: +# 1) "v0" (default) - the legacy fast sync implementation +# 2) "v1" - refactor of v0 version for better testability +# 2) "v2" - complete redesign of v0, optimized for testability & readability +version = "v0" + +####################################################### +### Consensus Configuration Options ### +####################################################### +[consensus] + +wal_file = "data/cs.wal/wal" + +# How long we wait for a proposal block before prevoting nil +timeout_propose = "3s" +# How much timeout_propose increases with each round +timeout_propose_delta = "500ms" +# How long we wait after receiving +2/3 prevotes for “anything” (ie. not a single block or nil) +timeout_prevote = "1s" +# How much the timeout_prevote increases with each round +timeout_prevote_delta = "500ms" +# How long we wait after receiving +2/3 precommits for “anything” (ie. not a single block or nil) +timeout_precommit = "1s" +# How much the timeout_precommit increases with each round +timeout_precommit_delta = "500ms" +# How long we wait after committing a block, before starting on the new +# height (this gives us a chance to receive some more precommits, even +# though we already have +2/3). +timeout_commit = "5s" + +# How many blocks to look back to check existence of the node's consensus votes before joining consensus +# When non-zero, the node will panic upon restart +# if the same consensus key was used to sign {double_sign_check_height} last blocks. +# So, validators should stop the state machine, wait for some blocks, and then restart the state machine to avoid panic. +double_sign_check_height = 0 + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +####################################################### +### Storage Configuration Options ### +####################################################### +[storage] + +# Set to true to discard ABCI responses from the state store, which can save a +# considerable amount of disk space. Set to false to ensure ABCI responses are +# persisted. ABCI responses are required for /block_results RPC queries, and to +# reindex events in the command-line tool. +discard_abci_responses = false + +####################################################### +### Transaction Indexer Configuration Options ### +####################################################### +[tx_index] + +# What indexer to use for transactions +# +# The application will set which txs to index. In some cases a node operator will be able +# to decide which txs to index based on configuration set in the application. +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +# - When "kv" is chosen "tx.height" and "tx.hash" will always be indexed. +# 3) "psql" - the indexer services backed by PostgreSQL. +# When "kv" or "psql" is chosen "tx.height" and "tx.hash" will always be indexed. +indexer = "kv" + +# The PostgreSQL connection configuration, the connection format: +# postgresql://:@:/? +psql-conn = "" + +####################################################### +### Instrumentation Configuration Options ### +####################################################### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/.docker/container-state/nucleus-testnet-data/config/genesis.json b/.docker/container-state/nucleus-testnet-data/config/genesis.json new file mode 100644 index 0000000000..57d87a8ef0 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/genesis.json @@ -0,0 +1,463 @@ +{ + "genesis_time": "2024-06-04T10:09:21.465680574Z", + "chain_id": "nucleus-testnet", + "initial_height": "1", + "consensus_params": { + "block": { + "max_bytes": "22020096", + "max_gas": "-1", + "time_iota_ms": "1000" + }, + "evidence": { + "max_age_num_blocks": "100000", + "max_age_duration": "172800000000000", + "max_bytes": "1048576" + }, + "validator": { + "pub_key_types": [ + "ed25519" + ] + }, + "version": {} + }, + "app_hash": "", + "app_state": { + "auth": { + "params": { + "max_memo_characters": "256", + "tx_sig_limit": "7", + "tx_size_cost_per_byte": "10", + "sig_verify_cost_ed25519": "590", + "sig_verify_cost_secp256k1": "1000" + }, + "accounts": [ + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "nuc15d4sf4z6y0vk9dnum8yzkvr9c3wq4q89hzvgjk", + "pub_key": null, + "account_number": "0", + "sequence": "0" + }, + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "nuc1yfygf0zr5s69ces9r0h72hqv23nkqz9nej732n", + "pub_key": null, + "account_number": "0", + "sequence": "0" + }, + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "nuc150evuj4j7k9kgu38e453jdv9m3u0ft2n4fgzfr", + "pub_key": null, + "account_number": "0", + "sequence": "0" + }, + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "nuc1qqwg79cpe2c9jtxklu8lxl30hj3qfzd3rzgs3x", + "pub_key": null, + "account_number": "0", + "sequence": "0" + }, + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "nuc1e0rx87mdj79zejewuc4jg7ql9ud2286g7x3t2z", + "pub_key": null, + "account_number": "0", + "sequence": "0" + }, + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "nuc1erfnkjsmalkwtvj44qnfr2drfzdt4n9ledw63y", + "pub_key": null, + "account_number": "0", + "sequence": "0" + }, + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "nuc1z609g9z8ef4jlkcfgg36c2ljgcmna50l77n078", + "pub_key": null, + "account_number": "0", + "sequence": "0" + } + ] + }, + "authz": { + "authorization": [] + }, + "bank": { + "params": { + "send_enabled": [], + "default_send_enabled": true + }, + "balances": [ + { + "address": "nuc1qqwg79cpe2c9jtxklu8lxl30hj3qfzd3rzgs3x", + "coins": [ + { + "denom": "ibc/F7F28FF3C09024A0225EDBBDB207E5872D2B4EF2FB874FE47B05EF9C9A7D211C", + "amount": "10000000000" + }, + { + "denom": "unucl", + "amount": "10000000000" + } + ] + }, + { + "address": "nuc1z609g9z8ef4jlkcfgg36c2ljgcmna50l77n078", + "coins": [ + { + "denom": "ibc/F7F28FF3C09024A0225EDBBDB207E5872D2B4EF2FB874FE47B05EF9C9A7D211C", + "amount": "10000000000" + }, + { + "denom": "unucl", + "amount": "10000000000" + } + ] + }, + { + "address": "nuc1yfygf0zr5s69ces9r0h72hqv23nkqz9nej732n", + "coins": [] + }, + { + "address": "nuc15d4sf4z6y0vk9dnum8yzkvr9c3wq4q89hzvgjk", + "coins": [ + { + "denom": "unucl", + "amount": "10000000000" + } + ] + }, + { + "address": "nuc150evuj4j7k9kgu38e453jdv9m3u0ft2n4fgzfr", + "coins": [ + { + "denom": "ibc/F7F28FF3C09024A0225EDBBDB207E5872D2B4EF2FB874FE47B05EF9C9A7D211C", + "amount": "10000000000" + }, + { + "denom": "unucl", + "amount": "10000000000" + } + ] + }, + { + "address": "nuc1erfnkjsmalkwtvj44qnfr2drfzdt4n9ledw63y", + "coins": [ + { + "denom": "ibc/F7F28FF3C09024A0225EDBBDB207E5872D2B4EF2FB874FE47B05EF9C9A7D211C", + "amount": "10000000000" + }, + { + "denom": "unucl", + "amount": "10000000000" + } + ] + }, + { + "address": "nuc1e0rx87mdj79zejewuc4jg7ql9ud2286g7x3t2z", + "coins": [ + { + "denom": "ibc/F7F28FF3C09024A0225EDBBDB207E5872D2B4EF2FB874FE47B05EF9C9A7D211C", + "amount": "10000000000" + }, + { + "denom": "unucl", + "amount": "10000000000" + } + ] + } + ], + "supply": [], + "denom_metadata": [ + { + "description": "The native staking token of Nucleus.", + "denom_units": [ + { + "denom": "unucl", + "exponent": 0, + "aliases": [ + "micronucl" + ] + }, + { + "denom": "mnucl", + "exponent": 3, + "aliases": [ + "millinucl" + ] + }, + { + "denom": "nucl", + "exponent": 6, + "aliases": [] + } + ], + "base": "unucl", + "display": "nucl", + "name": "", + "symbol": "", + "uri": "", + "uri_hash": "" + } + ] + }, + "capability": { + "index": "1", + "owners": [] + }, + "crisis": { + "constant_fee": { + "denom": "unucl", + "amount": "1000" + } + }, + "distribution": { + "params": { + "community_tax": "0.020000000000000000", + "base_proposer_reward": "0.010000000000000000", + "bonus_proposer_reward": "0.040000000000000000", + "withdraw_addr_enabled": true + }, + "fee_pool": { + "community_pool": [] + }, + "delegator_withdraw_infos": [], + "previous_proposer": "", + "outstanding_rewards": [], + "validator_accumulated_commissions": [], + "validator_historical_rewards": [], + "validator_current_rewards": [], + "delegator_starting_infos": [], + "validator_slash_events": [] + }, + "evidence": { + "evidence": [] + }, + "feegrant": { + "allowances": [] + }, + "genutil": { + "gen_txs": [ + { + "body": { + "messages": [ + { + "@type": "/cosmos.staking.v1beta1.MsgCreateValidator", + "description": { + "moniker": "nimda", + "identity": "", + "website": "", + "security_contact": "", + "details": "" + }, + "commission": { + "rate": "0.050000000000000000", + "max_rate": "0.200000000000000000", + "max_change_rate": "0.010000000000000000" + }, + "min_self_delegation": "1", + "delegator_address": "nuc15d4sf4z6y0vk9dnum8yzkvr9c3wq4q89hzvgjk", + "validator_address": "nucvaloper15d4sf4z6y0vk9dnum8yzkvr9c3wq4q897vefpu", + "pubkey": { + "@type": "/cosmos.crypto.ed25519.PubKey", + "key": "lw88sSAfwL1TDk4KEXxW/hZvYNxFEDUuI6LrS+tqsdo=" + }, + "value": { + "denom": "unucl", + "amount": "200000000" + } + } + ], + "memo": "12c7b58d43fe064e67fda22a2bd0598151308e32@192.168.1.24:26656", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AsgJRkBRhm9bOTsGtcByi+fBbdjEp6BB59AiFHDqDRN0" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_DIRECT" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [ + { + "denom": "unucl", + "amount": "2500" + } + ], + "gas_limit": "200000", + "payer": "", + "granter": "" + }, + "tip": null + }, + "signatures": [ + "yHNLaI5W2JnH5fa3Wn9Ma/YNt4HpeZkfCgRrl7EyX8svAKex8qDKJSeEl6zfUJSDiRYkkuPu67BOed8XUexA4w==" + ] + } + ] + }, + "gov": { + "starting_proposal_id": "1", + "deposits": [], + "votes": [], + "proposals": [], + "deposit_params": { + "min_deposit": [ + { + "denom": "unucl", + "amount": "10000000" + } + ], + "max_deposit_period": "172800s" + }, + "voting_params": { + "voting_period": "172800s" + }, + "tally_params": { + "quorum": "0.334000000000000000", + "threshold": "0.500000000000000000", + "veto_threshold": "0.334000000000000000" + } + }, + "group": { + "group_seq": "0", + "groups": [], + "group_members": [], + "group_policy_seq": "0", + "group_policies": [], + "proposal_seq": "0", + "proposals": [], + "votes": [] + }, + "htlc": { + "params": { + "asset_params": [] + }, + "htlcs": [], + "supplies": [], + "previous_block_time": "1970-01-01T00:00:01Z" + }, + "ibc": { + "client_genesis": { + "clients": [], + "clients_consensus": [], + "clients_metadata": [], + "params": { + "allowed_clients": [ + "06-solomachine", + "07-tendermint" + ] + }, + "create_localhost": false, + "next_client_sequence": "0" + }, + "connection_genesis": { + "connections": [], + "client_connection_paths": [], + "next_connection_sequence": "0", + "params": { + "max_expected_time_per_block": "30000000000" + } + }, + "channel_genesis": { + "channels": [], + "acknowledgements": [], + "commitments": [], + "receipts": [], + "send_sequences": [], + "recv_sequences": [], + "ack_sequences": [], + "next_channel_sequence": "0" + } + }, + "interchainaccounts": { + "controller_genesis_state": { + "active_channels": [], + "interchain_accounts": [], + "ports": [], + "params": { + "controller_enabled": true + } + }, + "host_genesis_state": { + "active_channels": [], + "interchain_accounts": [], + "port": "icahost", + "params": { + "host_enabled": true, + "allow_messages": [] + } + } + }, + "mint": { + "minter": { + "inflation": "0.130000000000000000", + "annual_provisions": "0.000000000000000000" + }, + "params": { + "mint_denom": "unucl", + "inflation_rate_change": "0.130000000000000000", + "inflation_max": "0.200000000000000000", + "inflation_min": "0.070000000000000000", + "goal_bonded": "0.670000000000000000", + "blocks_per_year": "6311520" + } + }, + "nucleus": { + "params": {} + }, + "params": null, + "slashing": { + "params": { + "signed_blocks_window": "100", + "min_signed_per_window": "0.500000000000000000", + "downtime_jail_duration": "600s", + "slash_fraction_double_sign": "0.050000000000000000", + "slash_fraction_downtime": "0.010000000000000000" + }, + "signing_infos": [], + "missed_blocks": [] + }, + "staking": { + "params": { + "unbonding_time": "1814400s", + "max_validators": 100, + "max_entries": 7, + "historical_entries": 10000, + "bond_denom": "unucl", + "min_commission_rate": "0.000000000000000000" + }, + "last_total_power": "0", + "last_validator_powers": [], + "validators": [], + "delegations": [], + "unbonding_delegations": [], + "redelegations": [], + "exported": false + }, + "transfer": { + "port_id": "transfer", + "denom_traces": [], + "params": { + "send_enabled": true, + "receive_enabled": true + } + }, + "upgrade": {}, + "vesting": {} + } +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/gentx/gentx-12c7b58d43fe064e67fda22a2bd0598151308e32.json b/.docker/container-state/nucleus-testnet-data/config/gentx/gentx-12c7b58d43fe064e67fda22a2bd0598151308e32.json new file mode 100644 index 0000000000..3de3e2b2be --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/gentx/gentx-12c7b58d43fe064e67fda22a2bd0598151308e32.json @@ -0,0 +1 @@ +{"body":{"messages":[{"@type":"/cosmos.staking.v1beta1.MsgCreateValidator","description":{"moniker":"nimda","identity":"","website":"","security_contact":"","details":""},"commission":{"rate":"0.050000000000000000","max_rate":"0.200000000000000000","max_change_rate":"0.010000000000000000"},"min_self_delegation":"1","delegator_address":"nuc15d4sf4z6y0vk9dnum8yzkvr9c3wq4q89hzvgjk","validator_address":"nucvaloper15d4sf4z6y0vk9dnum8yzkvr9c3wq4q897vefpu","pubkey":{"@type":"/cosmos.crypto.ed25519.PubKey","key":"lw88sSAfwL1TDk4KEXxW/hZvYNxFEDUuI6LrS+tqsdo="},"value":{"denom":"unucl","amount":"200000000"}}],"memo":"12c7b58d43fe064e67fda22a2bd0598151308e32@192.168.1.24:26656","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AsgJRkBRhm9bOTsGtcByi+fBbdjEp6BB59AiFHDqDRN0"},"mode_info":{"single":{"mode":"SIGN_MODE_DIRECT"}},"sequence":"0"}],"fee":{"amount":[{"denom":"unucl","amount":"2500"}],"gas_limit":"200000","payer":"","granter":""},"tip":null},"signatures":["yHNLaI5W2JnH5fa3Wn9Ma/YNt4HpeZkfCgRrl7EyX8svAKex8qDKJSeEl6zfUJSDiRYkkuPu67BOed8XUexA4w=="]} diff --git a/.docker/container-state/nucleus-testnet-data/config/node_key.json b/.docker/container-state/nucleus-testnet-data/config/node_key.json new file mode 100644 index 0000000000..1d8587df8d --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/node_key.json @@ -0,0 +1 @@ +{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"RFJZUn1lEIM490WPhRFp/ktmWxFIfp9ncLc9N8qwlYeGOnl+VaXhCKZ8yt1ad73NxnTkPfjoJ2L5FSvezlvAAA=="}} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/priv_validator_key.json b/.docker/container-state/nucleus-testnet-data/config/priv_validator_key.json new file mode 100644 index 0000000000..83e5195aca --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/priv_validator_key.json @@ -0,0 +1,11 @@ +{ + "address": "E0ADD4CA24ED5ED685BAFEAC1897CE2E63CF2B5E", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "lw88sSAfwL1TDk4KEXxW/hZvYNxFEDUuI6LrS+tqsdo=" + }, + "priv_key": { + "type": "tendermint/PrivKeyEd25519", + "value": "8H+X2GroxcbOntGYOPzKDqoQsA2du++m7UDtJzrf18uXDzyxIB/AvVMOTgoRfFb+Fm9g3EUQNS4joutL62qx2g==" + } +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-01059173227530771155 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-01059173227530771155 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-01059173227530771155 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-01554913484981479804 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-01554913484981479804 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-01554913484981479804 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-02330869765942450674 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-02330869765942450674 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-02330869765942450674 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-02840196522853014193 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-02840196522853014193 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-02840196522853014193 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-03684770830230437769 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-03684770830230437769 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-03684770830230437769 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-05951320143323737291 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-05951320143323737291 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-05951320143323737291 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-06122022161526377170 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-06122022161526377170 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-06122022161526377170 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-07519249100848775322 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-07519249100848775322 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-07519249100848775322 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-1868122991755536167 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-1868122991755536167 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-1868122991755536167 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-5942702362113201644 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-5942702362113201644 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-5942702362113201644 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-6555242139890704645 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-6555242139890704645 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-6555242139890704645 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-7440457262784007142 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-7440457262784007142 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-7440457262784007142 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-7683025105498428194 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-7683025105498428194 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-7683025105498428194 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000154.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000154.ldb new file mode 100644 index 0000000000..a8a9d5d14a Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/application.db/000154.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000155.log b/.docker/container-state/nucleus-testnet-data/data/application.db/000155.log new file mode 100644 index 0000000000..eb4ce2123b Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/application.db/000155.log differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000157.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000157.ldb new file mode 100644 index 0000000000..d2efabcda6 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/application.db/000157.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000158.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000158.ldb new file mode 100644 index 0000000000..0f54f43bcb Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/application.db/000158.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000159.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000159.ldb new file mode 100644 index 0000000000..cb9fafd62b Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/application.db/000159.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000160.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000160.ldb new file mode 100644 index 0000000000..a8417c6b23 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/application.db/000160.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/CURRENT b/.docker/container-state/nucleus-testnet-data/data/application.db/CURRENT new file mode 100644 index 0000000000..c16f179ffd --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/application.db/CURRENT @@ -0,0 +1 @@ +MANIFEST-000156 diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/CURRENT.bak b/.docker/container-state/nucleus-testnet-data/data/application.db/CURRENT.bak new file mode 100644 index 0000000000..1dec270d08 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/application.db/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000149 diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/LOCK b/.docker/container-state/nucleus-testnet-data/data/application.db/LOCK new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/LOG b/.docker/container-state/nucleus-testnet-data/data/application.db/LOG new file mode 100644 index 0000000000..d5977bfc9f --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/application.db/LOG @@ -0,0 +1,551 @@ +=============== Jun 4, 2024 (UTC) =============== +11:02:17.153019 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:02:17.155047 db@open opening +11:02:17.155230 version@stat F·[] S·0B[] Sc·[] +11:02:17.155840 db@janitor F·2 G·0 +11:02:17.155867 db@open done T·813.26µs +=============== Jun 6, 2024 (UTC) =============== +05:23:16.989830 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:23:16.989931 version@stat F·[] S·0B[] Sc·[] +05:23:16.989941 db@open opening +05:23:16.989964 journal@recovery F·1 +05:23:16.990199 journal@recovery recovering @1 +05:23:17.014526 memdb@flush created L0@2 N·26530 S·1MiB "s/1,v499":"s/p..hts,v630" +05:23:17.014655 version@stat F·[1] S·1MiB[1MiB] Sc·[0.25] +05:23:17.017692 db@janitor F·3 G·0 +05:23:17.017711 db@open done T·27.764757ms +=============== Jun 10, 2024 (UTC) =============== +10:09:48.947179 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:09:48.947301 version@stat F·[1] S·1MiB[1MiB] Sc·[0.25] +10:09:48.947313 db@open opening +10:09:48.947342 journal@recovery F·1 +10:09:48.947436 journal@recovery recovering @3 +10:09:48.975881 memdb@flush created L0@5 N·30645 S·1MiB "s/218,v26651":"s/p..hts,v26650" +10:09:48.976009 version@stat F·[2] S·3MiB[3MiB] Sc·[0.50] +10:09:48.979287 db@janitor F·4 G·0 +10:09:48.979305 db@open done T·31.98773ms +10:09:54.039001 table@compaction L0·2 -> L1·0 S·3MiB Q·57177 +10:09:54.054819 table@build created L1@8 N·32441 S·2MiB "s/1,v499":"s/k..&\x88},v26655" +10:09:54.059930 table@build created L1@9 N·10204 S·917KiB "s/k..Y\x9dN,v39456":"s/p..hts,v57174" +10:09:54.059953 version@stat F·[0 2] S·2MiB[0B 2MiB] Sc·[0.00 0.03] +10:09:54.060525 table@compaction committed F~ S-146KiB Ke·0 D·14530 T·21.507495ms +10:09:54.060815 table@remove removed @2 +=============== Jun 10, 2024 (UTC) =============== +10:19:47.804621 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:19:47.804763 version@stat F·[0 2] S·2MiB[0B 2MiB] Sc·[0.00 0.03] +10:19:47.804776 db@open opening +10:19:47.804807 journal@recovery F·1 +10:19:47.804989 journal@recovery recovering @6 +10:19:47.817928 memdb@flush created L0@10 N·14431 S·793KiB "s/465,v57299":"s/p..hts,v57298" +10:19:47.818058 version@stat F·[1 2] S·3MiB[793KiB 2MiB] Sc·[0.25 0.03] +10:19:47.820971 db@janitor F·6 G·1 +10:19:47.820979 db@janitor removing table-5 +10:19:47.821329 db@open done T·16.547569ms +=============== Jun 10, 2024 (UTC) =============== +10:20:11.668697 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:20:11.668820 version@stat F·[1 2] S·3MiB[793KiB 2MiB] Sc·[0.25 0.03] +10:20:11.668832 db@open opening +10:20:11.668862 journal@recovery F·1 +10:20:11.669038 journal@recovery recovering @11 +10:20:11.669815 version@stat F·[1 2] S·3MiB[793KiB 2MiB] Sc·[0.25 0.03] +10:20:11.672599 db@janitor F·5 G·0 +10:20:11.672617 db@open done T·3.779802ms +10:20:56.772142 table@compaction L0·1 -> L1·2 S·3MiB Q·72609 +10:20:56.785966 table@build created L1@15 N·28801 S·2MiB "s/1,v499":"s/k..\x00\x01\xd9,v58209" +10:20:56.796512 table@build created L1@16 N·24636 S·1MiB "s/k..\x00\x01\xda,v58391":"s/p..hts,v71606" +10:20:56.796534 version@stat F·[0 2] S·3MiB[0B 3MiB] Sc·[0.00 0.04] +10:20:56.797107 table@compaction committed F-1 S-26KiB Ke·0 D·3639 T·24.946359ms +10:20:56.797326 table@remove removed @10 +10:20:56.797749 table@remove removed @8 +10:20:56.797968 table@remove removed @9 +=============== Jun 10, 2024 (UTC) =============== +10:23:28.214423 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:23:28.214548 version@stat F·[0 2] S·3MiB[0B 3MiB] Sc·[0.00 0.04] +10:23:28.214560 db@open opening +10:23:28.214588 journal@recovery F·1 +10:23:28.214783 journal@recovery recovering @13 +10:23:28.218995 memdb@flush created L0@17 N·4323 S·239KiB "s/582,v71733":"s/p..hts,v71732" +10:23:28.219113 version@stat F·[1 2] S·3MiB[239KiB 3MiB] Sc·[0.25 0.04] +10:23:28.221785 db@janitor F·5 G·0 +10:23:28.221797 db@open done T·7.232311ms +10:24:48.367494 table@compaction L0·1 -> L1·2 S·3MiB Q·77796 +10:24:48.381066 table@build created L1@20 N·27688 S·2MiB "s/1,v499":"s/k..G\xdf?,v49145" +10:24:48.392891 table@build created L1@21 N·29007 S·1MiB "s/k..m\x9az,v49258":"s/p..hts,v75930" +10:24:48.392911 version@stat F·[0 2] S·3MiB[0B 3MiB] Sc·[0.00 0.04] +10:24:48.393500 table@compaction committed F-1 S-6KiB Ke·0 D·1065 T·25.988629ms +10:24:48.393922 table@remove removed @15 +10:24:48.394285 table@remove removed @16 +=============== Jun 10, 2024 (UTC) =============== +10:27:08.387537 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:27:08.387676 version@stat F·[0 2] S·3MiB[0B 3MiB] Sc·[0.00 0.04] +10:27:08.387689 db@open opening +10:27:08.387720 journal@recovery F·1 +10:27:08.387920 journal@recovery recovering @18 +10:27:08.392859 memdb@flush created L0@22 N·5238 S·287KiB "s/616,v76057":"s/p..hts,v76056" +10:27:08.392989 version@stat F·[1 2] S·4MiB[287KiB 3MiB] Sc·[0.25 0.04] +10:27:08.395685 db@janitor F·6 G·1 +10:27:08.395692 db@janitor removing table-17 +10:27:08.395775 db@open done T·8.079478ms +10:27:35.621747 table@compaction L0·1 -> L1·2 S·4MiB Q·81796 +10:27:35.635407 table@build created L1@25 N·26268 S·2MiB "s/1,v499":"s/k..C`\xd4,v16345" +10:27:35.648036 table@build created L1@26 N·30133 S·2MiB "s/k..\xe4,\",v16342":"s/k..\xf8J\xe6,v54390" +10:27:35.649902 table@build created L1@27 N·4226 S·139KiB "s/k..\xfe\x9c\x94,v54392":"s/p..hts,v81169" +10:27:35.649925 version@stat F·[0 3] S·4MiB[0B 4MiB] Sc·[0.00 0.04] +10:27:35.650493 table@compaction committed F~ S-3KiB Ke·0 D·1306 T·28.727581ms +10:27:35.650893 table@remove removed @20 +10:27:35.651281 table@remove removed @21 +=============== Jun 10, 2024 (UTC) =============== +10:30:28.003560 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:30:28.003680 version@stat F·[0 3] S·4MiB[0B 4MiB] Sc·[0.00 0.04] +10:30:28.003693 db@open opening +10:30:28.003727 journal@recovery F·1 +10:30:28.003923 journal@recovery recovering @23 +10:30:28.007014 memdb@flush created L0@28 N·2460 S·135KiB "s/658,v81296":"s/p..hts,v81295" +10:30:28.007134 version@stat F·[1 3] S·4MiB[135KiB 4MiB] Sc·[0.25 0.04] +10:30:28.009778 db@janitor F·7 G·1 +10:30:28.009786 db@janitor removing table-22 +10:30:28.009884 db@open done T·6.185242ms +10:31:48.160595 table@compaction L0·1 -> L1·3 S·4MiB Q·85502 +10:31:48.173851 table@build created L1@31 N·25661 S·2MiB "s/1,v499":"s/k..\v0\xf2,v1959" +10:31:48.186308 table@build created L1@32 N·29638 S·2MiB "s/k..\x87\x87\xf7,v1957":"s/k..\x99,\x1c,v18646" +10:31:48.189180 table@build created L1@33 N·7189 S·270KiB "s/k...\x86\xde,v18643":"s/p..hts,v83630" +10:31:48.189210 version@stat F·[0 3] S·4MiB[0B 4MiB] Sc·[0.00 0.04] +10:31:48.189782 table@compaction committed F-1 S-3KiB Ke·0 D·599 T·29.167911ms +10:31:48.190223 table@remove removed @25 +10:31:48.190608 table@remove removed @26 +10:31:48.190657 table@remove removed @27 +=============== Jun 10, 2024 (UTC) =============== +11:11:44.577240 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:11:44.577369 version@stat F·[0 3] S·4MiB[0B 4MiB] Sc·[0.00 0.04] +11:11:44.577432 db@open opening +11:11:44.577518 journal@recovery F·1 +11:11:44.577726 journal@recovery recovering @29 +11:11:44.583326 memdb@flush created L0@34 N·6241 S·343KiB "s/677,v83757":"s/p..hts,v83756" +11:11:44.583448 version@stat F·[1 3] S·4MiB[343KiB 4MiB] Sc·[0.25 0.04] +11:11:44.586146 db@janitor F·7 G·1 +11:11:44.586154 db@janitor removing table-28 +11:11:44.586222 db@open done T·8.779793ms +11:12:19.677457 table@compaction L0·1 -> L1·3 S·4MiB Q·90624 +11:12:19.690805 table@build created L1@37 N·25669 S·2MiB "s/1,v499":"s/k..S\x93t,v12167" +11:12:19.703741 table@build created L1@38 N·30785 S·2MiB "s/k..\xda\x15\x1f,v78135":"s/k..^JZ,v21323" +11:12:19.708261 table@build created L1@39 N·10720 S·612KiB "s/k..\x035\xd4,v43808":"s/p..hts,v89872" +11:12:19.708290 version@stat F·[0 3] S·4MiB[0B 4MiB] Sc·[0.00 0.05] +11:12:19.708865 table@compaction committed F-1 S-2KiB Ke·0 D·1555 T·31.384841ms +11:12:19.709277 table@remove removed @31 +11:12:19.709690 table@remove removed @32 +11:12:19.709815 table@remove removed @33 +=============== Jun 10, 2024 (UTC) =============== +11:15:00.368723 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:15:00.368844 version@stat F·[0 3] S·4MiB[0B 4MiB] Sc·[0.00 0.05] +11:15:00.368858 db@open opening +11:15:00.368892 journal@recovery F·1 +11:15:00.368995 journal@recovery recovering @35 +11:15:00.372581 memdb@flush created L0@40 N·3461 S·198KiB "s/727,v89999":"s/p..hts,v89998" +11:15:00.372719 version@stat F·[1 3] S·4MiB[198KiB 4MiB] Sc·[0.25 0.05] +11:15:00.376163 db@janitor F·7 G·1 +11:15:00.376173 db@janitor removing table-34 +11:15:00.376313 db@open done T·7.447901ms +=============== Jun 10, 2024 (UTC) =============== +11:21:08.195628 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:21:08.195754 version@stat F·[1 3] S·4MiB[198KiB 4MiB] Sc·[0.25 0.05] +11:21:08.195767 db@open opening +11:21:08.195798 journal@recovery F·1 +11:21:08.195975 journal@recovery recovering @41 +11:21:08.196426 version@stat F·[1 3] S·4MiB[198KiB 4MiB] Sc·[0.25 0.05] +11:21:08.199202 db@janitor F·6 G·0 +11:21:08.199220 db@open done T·3.448269ms +=============== Jun 10, 2024 (UTC) =============== +11:22:28.257213 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:22:28.257369 version@stat F·[1 3] S·4MiB[198KiB 4MiB] Sc·[0.25 0.05] +11:22:28.257384 db@open opening +11:22:28.257428 journal@recovery F·1 +11:22:28.257547 journal@recovery recovering @43 +11:22:28.258465 memdb@flush created L0@45 N·125 S·7KiB "s/751,v93461":"s/p..hts,v93460" +11:22:28.258779 version@stat F·[2 3] S·4MiB[206KiB 4MiB] Sc·[0.50 0.05] +11:22:28.262873 db@janitor F·7 G·0 +11:22:28.262892 db@open done T·5.501516ms +11:22:33.315933 table@compaction L0·2 -> L1·3 S·4MiB Q·93463 +11:22:33.329447 table@build created L1@48 N·25736 S·2MiB "s/1,v499":"s/k..@\x9fi,v27903" +11:22:33.343087 table@build created L1@49 N·31857 S·2MiB "s/k..^\x8f\\,v30966":"s/k..\az\xa0,v42094" +11:22:33.348593 table@build created L1@50 N·12352 S·812KiB "s/k..e.\xff,v90474":"s/p..hts,v93460" +11:22:33.348622 version@stat F·[0 3] S·4MiB[0B 4MiB] Sc·[0.00 0.05] +11:22:33.349202 table@compaction committed F-2 S-3KiB Ke·0 D·815 T·33.250577ms +11:22:33.349295 table@remove removed @40 +11:22:33.349660 table@remove removed @37 +11:22:33.350060 table@remove removed @38 +11:22:33.350239 table@remove removed @39 +=============== Jun 10, 2024 (UTC) =============== +11:23:15.225956 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:23:15.226075 version@stat F·[0 3] S·4MiB[0B 4MiB] Sc·[0.00 0.05] +11:23:15.226088 db@open opening +11:23:15.226123 journal@recovery F·1 +11:23:15.226222 journal@recovery recovering @46 +11:23:15.227403 memdb@flush created L0@51 N·625 S·33KiB "s/752,v93587":"s/p..hts,v93586" +11:23:15.227683 version@stat F·[1 3] S·4MiB[33KiB 4MiB] Sc·[0.25 0.05] +11:23:15.230467 db@janitor F·7 G·1 +11:23:15.230483 db@janitor removing table-45 +11:23:15.230525 db@open done T·4.432437ms +11:23:45.311352 table@compaction L0·1 -> L1·3 S·4MiB Q·94715 +11:23:45.325053 table@build created L1@54 N·25698 S·2MiB "s/1,v499":"s/k..\xb4G\xad,v42131" +11:23:45.338343 table@build created L1@55 N·31986 S·2MiB "s/k..4\xfaD,v56281":"s/k..E\xe4T,v35729" +11:23:45.343864 table@build created L1@56 N·12731 S·857KiB "s/k..\xcba\xc7,v65071":"s/p..hts,v94086" +11:23:45.343889 version@stat F·[0 3] S·4MiB[0B 4MiB] Sc·[0.00 0.05] +11:23:45.344461 table@compaction committed F-1 S+7KiB Ke·0 D·155 T·33.091217ms +11:23:45.344880 table@remove removed @48 +11:23:45.345336 table@remove removed @49 +11:23:45.345490 table@remove removed @50 +=============== Jun 10, 2024 (UTC) =============== +11:29:08.589479 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:29:08.589612 version@stat F·[0 3] S·4MiB[0B 4MiB] Sc·[0.00 0.05] +11:29:08.589628 db@open opening +11:29:08.589660 journal@recovery F·1 +11:29:08.589860 journal@recovery recovering @52 +11:29:08.597140 memdb@flush created L0@57 N·8233 S·457KiB "s/757,v94213":"s/p..hts,v94212" +11:29:08.597275 version@stat F·[1 3] S·5MiB[457KiB 4MiB] Sc·[0.25 0.05] +11:29:08.600093 db@janitor F·7 G·1 +11:29:08.600103 db@janitor removing table-51 +11:29:08.600151 db@open done T·10.518089ms +11:29:53.705206 table@compaction L0·1 -> L1·3 S·5MiB Q·103324 +11:29:53.720168 table@build created L1@60 N·25880 S·2MiB "s/1,v499":"s/k..\x03ړ,v94566" +11:29:53.734810 table@build created L1@61 N·34394 S·2MiB "s/k..\xeaχ,v17268":"s/k..\x17\xaf\xe4,v42101" +11:29:53.742516 table@build created L1@62 N·16321 S·1MiB "s/k..=X7,v94523":"s/p..hts,v102320" +11:29:53.742548 version@stat F·[0 3] S·5MiB[0B 5MiB] Sc·[0.00 0.05] +11:29:53.743118 table@compaction committed F-1 S-18KiB Ke·0 D·2053 T·37.884368ms +11:29:53.743548 table@remove removed @54 +11:29:53.743948 table@remove removed @55 +11:29:53.744162 table@remove removed @56 +=============== Jun 10, 2024 (UTC) =============== +11:30:42.272443 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:42.272566 version@stat F·[0 3] S·5MiB[0B 5MiB] Sc·[0.00 0.05] +11:30:42.272580 db@open opening +11:30:42.272613 journal@recovery F·1 +11:30:42.272709 journal@recovery recovering @58 +11:30:42.275163 memdb@flush created L0@63 N·2126 S·114KiB "s/823,v102447":"s/p..hts,v102446" +11:30:42.275287 version@stat F·[1 3] S·5MiB[114KiB 5MiB] Sc·[0.25 0.05] +11:30:42.277900 db@janitor F·7 G·1 +11:30:42.277909 db@janitor removing table-57 +11:30:42.278025 db@open done T·5.440236ms +=============== Jun 10, 2024 (UTC) =============== +11:30:55.658631 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:55.658763 version@stat F·[1 3] S·5MiB[114KiB 5MiB] Sc·[0.25 0.05] +11:30:55.658776 db@open opening +11:30:55.658809 journal@recovery F·1 +11:30:55.658898 journal@recovery recovering @64 +11:30:55.660051 memdb@flush created L0@66 N·126 S·8KiB "s/840,v104575":"s/p..hts,v104573" +11:30:55.660321 version@stat F·[2 3] S·5MiB[122KiB 5MiB] Sc·[0.50 0.05] +11:30:55.665424 db@janitor F·7 G·0 +11:30:55.665451 db@open done T·6.666506ms +11:31:00.721511 table@compaction L0·2 -> L1·3 S·5MiB Q·104577 +11:31:00.735564 table@build created L1@69 N·25918 S·2MiB "s/1,v499":"s/k..\xf6\x12\xf1,v90096" +11:31:00.750734 table@build created L1@70 N·35025 S·2MiB "s/k..|\x7f\x16,v12531":"s/k..3\xfa\x1f,v12954" +11:31:00.759099 table@build created L1@71 N·17344 S·1MiB "s/k..\x80iJ,v82942":"s/p..hts,v104574" +11:31:00.759122 version@stat F·[0 3] S·5MiB[0B 5MiB] Sc·[0.00 0.05] +11:31:00.759701 table@compaction committed F-2 S+4KiB Ke·0 D·560 T·38.1687ms +11:31:00.759781 table@remove removed @63 +11:31:00.760155 table@remove removed @60 +11:31:00.760594 table@remove removed @61 +11:31:00.760890 table@remove removed @62 +=============== Jun 10, 2024 (UTC) =============== +11:36:36.044137 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:36:36.044283 version@stat F·[0 3] S·5MiB[0B 5MiB] Sc·[0.00 0.05] +11:36:36.044302 db@open opening +11:36:36.044363 journal@recovery F·1 +11:36:36.044531 journal@recovery recovering @67 +11:36:36.051282 memdb@flush created L0@72 N·7488 S·410KiB "s/841,v104701":"s/p..hts,v104700" +11:36:36.051415 version@stat F·[1 3] S·5MiB[410KiB 5MiB] Sc·[0.25 0.05] +11:36:36.054655 db@janitor F·7 G·1 +11:36:36.054664 db@janitor removing table-66 +11:36:36.054706 db@open done T·10.398727ms +11:37:21.163801 table@compaction L0·1 -> L1·3 S·5MiB Q·113064 +11:37:21.177861 table@build created L1@75 N·26109 S·2MiB "s/1,v499":"s/k.._\xc2H,v105793" +11:37:21.193638 table@build created L1@76 N·37117 S·2MiB "s/k..\x15\x00Z,v48886":"s/k../B\xcf,v30518" +11:37:21.203733 table@build created L1@77 N·20683 S·1MiB "s/k..\xd5cc,v98974":"s/p..hts,v112063" +11:37:21.203758 version@stat F·[0 3] S·5MiB[0B 5MiB] Sc·[0.00 0.06] +11:37:21.204327 table@compaction committed F-1 S-7KiB Ke·0 D·1866 T·40.506749ms +11:37:21.204748 table@remove removed @69 +11:37:21.205181 table@remove removed @70 +11:37:21.205507 table@remove removed @71 +=============== Jun 10, 2024 (UTC) =============== +12:01:04.928335 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:01:04.928474 version@stat F·[0 3] S·5MiB[0B 5MiB] Sc·[0.00 0.06] +12:01:04.928493 db@open opening +12:01:04.928537 journal@recovery F·1 +12:01:04.928634 journal@recovery recovering @73 +12:01:04.934234 memdb@flush created L0@78 N·6235 S·344KiB "s/901,v112188":"s/p..hts,v112187" +12:01:04.934551 version@stat F·[1 3] S·6MiB[344KiB 5MiB] Sc·[0.25 0.06] +12:01:04.937539 db@janitor F·7 G·1 +12:01:04.937550 db@janitor removing table-72 +12:01:04.937667 db@open done T·9.167731ms +=============== Jun 10, 2024 (UTC) =============== +12:02:59.924405 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:02:59.924546 version@stat F·[1 3] S·6MiB[344KiB 5MiB] Sc·[0.25 0.06] +12:02:59.924564 db@open opening +12:02:59.924601 journal@recovery F·1 +12:02:59.924874 journal@recovery recovering @79 +12:02:59.926525 memdb@flush created L0@81 N·375 S·19KiB "s/951,v118426":"s/p..hts,v118425" +12:02:59.926683 version@stat F·[2 3] S·6MiB[364KiB 5MiB] Sc·[0.50 0.06] +12:02:59.929424 db@janitor F·7 G·0 +12:02:59.929446 db@open done T·4.875913ms +12:03:04.985928 table@compaction L0·2 -> L1·3 S·6MiB Q·118678 +12:03:05.000541 table@build created L1@84 N·26214 S·2MiB "s/1,v499":"s/k..Og\xc3,v45444" +12:03:05.017051 table@build created L1@85 N·39244 S·2MiB "s/k..\x86\x8f\x81,v23591":"s/k..705,v87236" +12:03:05.027971 table@build created L1@86 N·19330 S·2MiB "s/k..706,v87333":"s/k..\x80\x15\xaf,v105420" +12:03:05.029777 table@build created L1@87 N·4083 S·111KiB "s/k..\xc90A,v105423":"s/p..hts,v118675" +12:03:05.029803 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.06] +12:03:05.030919 table@compaction committed F-1 S-7KiB Ke·0 D·1648 T·44.962277ms +12:03:05.031043 table@remove removed @78 +12:03:05.031427 table@remove removed @75 +12:03:05.031838 table@remove removed @76 +12:03:05.032198 table@remove removed @77 +=============== Jun 10, 2024 (UTC) =============== +12:03:19.578037 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:19.578172 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.06] +12:03:19.578197 db@open opening +12:03:19.578235 journal@recovery F·1 +12:03:19.578450 journal@recovery recovering @82 +12:03:19.585879 memdb@flush created L0@88 N·250 S·13KiB "s/954,v118802":"s/p..hts,v118801" +12:03:19.586024 version@stat F·[1 4] S·6MiB[13KiB 6MiB] Sc·[0.25 0.06] +12:03:19.589565 db@janitor F·8 G·1 +12:03:19.589581 db@janitor removing table-81 +12:03:19.589635 db@open done T·11.431501ms +=============== Jun 10, 2024 (UTC) =============== +12:04:23.511322 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:04:23.511484 version@stat F·[1 4] S·6MiB[13KiB 6MiB] Sc·[0.25 0.06] +12:04:23.511498 db@open opening +12:04:23.511539 journal@recovery F·1 +12:04:23.511708 journal@recovery recovering @89 +12:04:23.513097 memdb@flush created L0@91 N·125 S·7KiB "s/956,v119053":"s/p..hts,v119052" +12:04:23.515002 version@stat F·[2 4] S·6MiB[21KiB 6MiB] Sc·[0.50 0.06] +12:04:23.517725 db@janitor F·8 G·0 +12:04:23.517743 db@open done T·6.239135ms +=============== Jun 10, 2024 (UTC) =============== +12:07:46.637779 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:07:46.637910 version@stat F·[2 4] S·6MiB[21KiB 6MiB] Sc·[0.50 0.06] +12:07:46.637926 db@open opening +12:07:46.637967 journal@recovery F·1 +12:07:46.638064 journal@recovery recovering @92 +12:07:46.638407 version@stat F·[2 4] S·6MiB[21KiB 6MiB] Sc·[0.50 0.06] +12:07:46.642735 db@janitor F·8 G·0 +12:07:46.642764 db@open done T·4.823572ms +=============== Jun 10, 2024 (UTC) =============== +12:08:02.035051 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:08:02.035205 version@stat F·[2 4] S·6MiB[21KiB 6MiB] Sc·[0.50 0.06] +12:08:02.035223 db@open opening +12:08:02.035260 journal@recovery F·1 +12:08:02.035480 journal@recovery recovering @94 +12:08:02.035726 version@stat F·[2 4] S·6MiB[21KiB 6MiB] Sc·[0.50 0.06] +12:08:02.038668 db@janitor F·8 G·0 +12:08:02.038693 db@open done T·3.457031ms +=============== Jun 10, 2024 (UTC) =============== +12:27:21.378533 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:27:21.378667 version@stat F·[2 4] S·6MiB[21KiB 6MiB] Sc·[0.50 0.06] +12:27:21.378681 db@open opening +12:27:21.378720 journal@recovery F·1 +12:27:21.378935 journal@recovery recovering @96 +12:27:21.379433 version@stat F·[2 4] S·6MiB[21KiB 6MiB] Sc·[0.50 0.06] +12:27:21.383910 db@janitor F·8 G·0 +12:27:21.383942 db@open done T·5.247126ms +12:27:21.497937 table@compaction L0·2 -> L1·4 S·6MiB Q·119055 +12:27:21.514083 table@build created L1@100 N·26210 S·2MiB "s/1,v499":"s/k..\xf0.^,v28181" +12:27:21.531654 table@build created L1@101 N·39393 S·2MiB "s/k..c\xa8\xc9,v81378":"s/k..658,v81198" +12:27:21.542467 table@build created L1@102 N·18895 S·2MiB "s/k..659,v81357":"s/k..%U\xa1,v99302" +12:27:21.544653 table@build created L1@103 N·4655 S·136KiB "s/k..\xbfZx,v99304":"s/p..hts,v119052" +12:27:21.544690 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.06] +12:27:21.546405 table@compaction committed F-2 S+1KiB Ke·0 D·93 T·48.446441ms +12:27:21.546477 table@remove removed @91 +12:27:21.546513 table@remove removed @88 +12:27:21.546937 table@remove removed @84 +12:27:21.547413 table@remove removed @85 +12:27:21.547838 table@remove removed @86 +12:27:21.547886 table@remove removed @87 +=============== Jun 10, 2024 (UTC) =============== +12:56:43.749314 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:56:43.749428 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.06] +12:56:43.749442 db@open opening +12:56:43.749474 journal@recovery F·1 +12:56:43.749678 journal@recovery recovering @98 +12:56:43.750993 memdb@flush created L0@104 N·250 S·13KiB "s/957,v119179":"s/p..hts,v119178" +12:56:43.751146 version@stat F·[1 4] S·6MiB[13KiB 6MiB] Sc·[0.25 0.06] +12:56:43.754258 db@janitor F·7 G·0 +12:56:43.754276 db@open done T·4.828302ms +=============== Jun 10, 2024 (UTC) =============== +12:57:38.320984 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:57:38.321104 version@stat F·[1 4] S·6MiB[13KiB 6MiB] Sc·[0.25 0.06] +12:57:38.321118 db@open opening +12:57:38.321153 journal@recovery F·1 +12:57:38.321256 journal@recovery recovering @105 +12:57:38.321538 version@stat F·[1 4] S·6MiB[13KiB 6MiB] Sc·[0.25 0.06] +12:57:38.324489 db@janitor F·7 G·0 +12:57:38.324510 db@open done T·3.38598ms +=============== Jun 10, 2024 (UTC) =============== +12:58:01.414316 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:01.414448 version@stat F·[1 4] S·6MiB[13KiB 6MiB] Sc·[0.25 0.06] +12:58:01.414461 db@open opening +12:58:01.414496 journal@recovery F·1 +12:58:01.414755 journal@recovery recovering @107 +12:58:01.416967 memdb@flush created L0@109 N·125 S·8KiB "s/959,v119430":"s/p..hts,v119429" +12:58:01.417101 version@stat F·[2 4] S·6MiB[21KiB 6MiB] Sc·[0.50 0.06] +12:58:01.420682 db@janitor F·8 G·0 +12:58:01.420702 db@open done T·6.232364ms +12:58:06.474695 table@compaction L0·2 -> L1·4 S·6MiB Q·119432 +12:58:06.488984 table@build created L1@112 N·26268 S·2MiB "s/1,v499":"s/k..F\rz,v16445" +12:58:06.505239 table@build created L1@113 N·39510 S·2MiB "s/k..\x9eE\xab,v60804":"s/k..637,v78665" +12:58:06.515776 table@build created L1@114 N·18673 S·2MiB "s/k..638,v78730":"s/k..p\x05U,v96071" +12:58:06.517908 table@build created L1@115 N·4984 S·150KiB "s/k..\xa7M\xf6,v96065":"s/p..hts,v119429" +12:58:06.517931 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.06] +12:58:06.518521 table@compaction committed F-2 S-5KiB Ke·0 D·93 T·43.804781ms +12:58:06.518585 table@remove removed @104 +12:58:06.518960 table@remove removed @100 +12:58:06.519357 table@remove removed @101 +12:58:06.519779 table@remove removed @102 +12:58:06.519830 table@remove removed @103 +=============== Jun 10, 2024 (UTC) =============== +12:58:48.938350 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:48.938471 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.06] +12:58:48.938486 db@open opening +12:58:48.938523 journal@recovery F·1 +12:58:48.938615 journal@recovery recovering @110 +12:58:48.939691 memdb@flush created L0@116 N·251 S·13KiB "s/960,v119557":"s/p..hts,v119555" +12:58:48.939982 version@stat F·[1 4] S·6MiB[13KiB 6MiB] Sc·[0.25 0.06] +12:58:48.943323 db@janitor F·8 G·1 +12:58:48.943340 db@janitor removing table-109 +12:58:48.943385 db@open done T·4.892942ms +=============== Jun 10, 2024 (UTC) =============== +12:59:05.179741 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:05.179892 version@stat F·[1 4] S·6MiB[13KiB 6MiB] Sc·[0.25 0.06] +12:59:05.179912 db@open opening +12:59:05.179961 journal@recovery F·1 +12:59:05.180083 journal@recovery recovering @117 +12:59:05.180459 version@stat F·[1 4] S·6MiB[13KiB 6MiB] Sc·[0.25 0.06] +12:59:05.185454 db@janitor F·7 G·0 +12:59:05.185482 db@open done T·5.556219ms +=============== Jun 10, 2024 (UTC) =============== +12:59:32.364226 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:32.364382 version@stat F·[1 4] S·6MiB[13KiB 6MiB] Sc·[0.25 0.06] +12:59:32.364397 db@open opening +12:59:32.364436 journal@recovery F·1 +12:59:32.364628 journal@recovery recovering @119 +12:59:32.365864 memdb@flush created L0@121 N·125 S·7KiB "s/962,v119808":"s/p..hts,v119807" +12:59:32.366062 version@stat F·[2 4] S·6MiB[21KiB 6MiB] Sc·[0.50 0.06] +12:59:32.369313 db@janitor F·8 G·0 +12:59:32.369333 db@open done T·4.930333ms +12:59:32.482085 table@compaction L0·2 -> L1·4 S·6MiB Q·119810 +12:59:32.496446 table@build created L1@124 N·26224 S·2MiB "s/1,v499":"s/k..\xa0\x02\xe4,v4174" +12:59:32.512989 table@build created L1@125 N·39687 S·2MiB "s/k..\xc6*$,v92946":"s/k..579,v71349" +12:59:32.524159 table@build created L1@126 N·18356 S·2MiB "s/k..P58,v6928":"s/k..l\xf4#,v90644" +12:59:32.526466 table@build created L1@127 N·5450 S·172KiB "s/k..:p\xda,v90642":"s/p..hts,v119807" +12:59:32.526491 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.06] +12:59:32.527069 table@compaction committed F-2 S-1KiB Ke·0 D·94 T·44.96445ms +12:59:32.527130 table@remove removed @116 +12:59:32.527519 table@remove removed @112 +12:59:32.527940 table@remove removed @113 +12:59:32.528413 table@remove removed @114 +12:59:32.528468 table@remove removed @115 +=============== Jul 3, 2024 (UTC) =============== +17:52:58.287231 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:52:58.287362 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.06] +17:52:58.287372 db@open opening +17:52:58.287410 journal@recovery F·1 +17:52:58.287601 journal@recovery recovering @122 +17:52:58.288202 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.06] +17:52:58.291869 db@janitor F·7 G·1 +17:52:58.291876 db@janitor removing table-121 +17:52:58.291918 db@open done T·4.541677ms +=============== Jul 3, 2024 (UTC) =============== +17:59:01.655850 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:59:01.655977 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.06] +17:59:01.655990 db@open opening +17:59:01.656024 journal@recovery F·1 +17:59:01.656280 journal@recovery recovering @128 +17:59:01.664160 memdb@flush created L0@130 N·9211 S·526KiB "s/1000,v124559":"s/p..hts,v119933" +17:59:01.664278 version@stat F·[1 4] S·6MiB[526KiB 6MiB] Sc·[0.25 0.06] +17:59:01.667135 db@janitor F·7 G·0 +17:59:01.667155 db@open done T·11.159872ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.905554 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.905681 version@stat F·[1 4] S·6MiB[526KiB 6MiB] Sc·[0.25 0.06] +05:10:40.905693 db@open opening +05:10:40.905729 journal@recovery F·1 +05:10:40.905932 journal@recovery recovering @131 +05:10:40.907646 memdb@flush created L0@133 N·625 S·34KiB "s/1032,v129146":"s/p..hts,v129145" +05:10:40.907787 version@stat F·[2 4] S·6MiB[560KiB 6MiB] Sc·[0.50 0.06] +05:10:40.912478 db@janitor F·8 G·0 +05:10:40.912492 db@open done T·6.794072ms +05:10:40.980773 table@compaction L0·2 -> L1·4 S·6MiB Q·129648 +05:10:40.997373 table@build created L1@136 N·24265 S·2MiB "s/1,v499":"s/k..\xbb\x1da,v114109" +05:10:41.023402 table@build created L1@137 N·42517 S·2MiB "s/k..\v\xd1\xd9,v114104":"s/k..\x926\x81,v45485" +05:10:41.037226 table@build created L1@138 N·15792 S·2MiB "s/k..\xce\t\xe8,v127197":"s/k..A\xb8j,v10829" +05:10:41.043103 table@build created L1@139 N·14633 S·724KiB "s/k..\x0f\xbb',v65315":"s/p..hts,v129645" +05:10:41.043137 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.07] +05:10:41.043726 table@compaction committed F-2 S-8KiB Ke·0 D·2346 T·62.926263ms +05:10:41.043888 table@remove removed @130 +05:10:41.044256 table@remove removed @124 +05:10:41.044737 table@remove removed @125 +05:10:41.045200 table@remove removed @126 +05:10:41.045256 table@remove removed @127 +=============== Oct 17, 2024 (UTC) =============== +06:44:58.635757 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.635904 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.07] +06:44:58.635919 db@open opening +06:44:58.635952 journal@recovery F·1 +06:44:58.636043 journal@recovery recovering @134 +06:44:58.675589 memdb@flush created L0@140 N·18960 S·1MiB "s/1037,v129772":"s/p..hts,v129771" +06:44:58.675735 version@stat F·[1 4] S·7MiB[1MiB 6MiB] Sc·[0.25 0.07] +06:44:58.690561 db@janitor F·8 G·1 +06:44:58.690567 db@janitor removing table-133 +06:44:58.690608 db@open done T·54.685771ms +06:45:23.851285 table@compaction L0·1 -> L1·4 S·7MiB Q·149292 +06:45:23.863708 table@build created L1@143 N·20853 S·2MiB "s/1,v499":"s/k..\x90\xb6\xf1,v46843" +06:45:23.878194 table@build created L1@144 N·34793 S·2MiB "s/k..^\xb3\xfc,v46849":"s/k..\xf5X*,v124880" +06:45:23.891282 table@build created L1@145 N·32649 S·2MiB "s/k..\\\x8d?,v124877":"s/k..\xf6OO,v108777" +06:45:23.901184 table@build created L1@146 N·23324 S·1MiB "s/k..\xbe\x90\x84,v72822":"s/p..hts,v148606" +06:45:23.901206 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:45:23.901783 table@compaction committed F-1 S-22KiB Ke·0 D·4548 T·50.479285ms +06:45:23.902188 table@remove removed @136 +06:45:23.902594 table@remove removed @137 +06:45:23.903127 table@remove removed @138 +06:45:23.903277 table@remove removed @139 +=============== Oct 17, 2024 (UTC) =============== +06:45:46.632694 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.632829 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:45:46.632841 db@open opening +06:45:46.632873 journal@recovery F·1 +06:45:46.633065 journal@recovery recovering @141 +06:45:46.634967 memdb@flush created L0@147 N·1058 S·59KiB "s/1181,v148733":"s/p..hts,v148732" +06:45:46.635106 version@stat F·[1 4] S·7MiB[59KiB 7MiB] Sc·[0.25 0.08] +06:45:46.637686 db@janitor F·8 G·1 +06:45:46.637692 db@janitor removing table-140 +06:45:46.637923 db@open done T·5.078403ms +06:46:51.996792 table@compaction L0·1 -> L1·4 S·7MiB Q·151169 +06:46:52.036950 table@build created L1@150 N·20616 S·2MiB "s/1,v499":"s/k..w\x17D,v41895" +06:46:52.085978 table@build created L1@151 N·34742 S·2MiB "s/k..\x02F\xf2,v41900":"s/k..\xffQ{,v117451" +06:46:52.135406 table@build created L1@152 N·33250 S·2MiB "s/k..\x96\xeeu,v117450":"s/k..\x1c\xb9t,v75679" +06:46:52.173716 table@build created L1@153 N·23832 S·1MiB "s/k..{\x9f\xd9,v41367":"s/p..hts,v149665" +06:46:52.173757 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:46:52.181047 table@compaction committed F-1 S+1KiB Ke·0 D·237 T·184.227992ms +06:46:52.181530 table@remove removed @143 +06:46:52.182010 table@remove removed @144 +06:46:52.182474 table@remove removed @145 +06:46:52.182906 table@remove removed @146 +=============== Oct 17, 2024 (UTC) =============== +06:47:26.253548 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.253681 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:47:26.253693 db@open opening +06:47:26.253724 journal@recovery F·1 +06:47:26.253926 journal@recovery recovering @148 +06:47:26.256527 memdb@flush created L0@154 N·1873 S·100KiB "s/1188,v149792":"s/p..hts,v149791" +06:47:26.256677 version@stat F·[1 4] S·7MiB[100KiB 7MiB] Sc·[0.25 0.08] +06:47:26.259838 db@janitor F·8 G·1 +06:47:26.259845 db@janitor removing table-147 +06:47:26.259895 db@open done T·6.196873ms +06:47:51.346605 table@compaction L0·1 -> L1·4 S·7MiB Q·152042 +06:47:51.358754 table@build created L1@157 N·20319 S·2MiB "s/1,v499":"s/k..7\xcc\xf2,v36191" +06:47:51.372848 table@build created L1@158 N·34591 S·2MiB "s/k..8\x00\x1e,v36192":"s/k..\xfd<\x97,v106679" +06:47:51.386025 table@build created L1@159 N·34293 S·2MiB "s/k..\xe4\x8ej,v106681":"s/k..@m\xdb,v129528" +06:47:51.396388 table@build created L1@160 N·24643 S·1MiB "s/k..\xdb\xee\x8f,v145753":"s/p..hts,v151539" +06:47:51.396408 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:47:51.396980 table@compaction committed F-1 S-1KiB Ke·0 D·467 T·50.358509ms +06:47:51.397411 table@remove removed @150 +06:47:51.397858 table@remove removed @151 +06:47:51.398295 table@remove removed @152 +06:47:51.398626 table@remove removed @153 diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/MANIFEST-000156 b/.docker/container-state/nucleus-testnet-data/data/application.db/MANIFEST-000156 new file mode 100644 index 0000000000..7da12a9d2f Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/application.db/MANIFEST-000156 differ diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000104.ldb b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000104.ldb new file mode 100644 index 0000000000..c4e0ac3f50 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000104.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000105.log b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000105.log new file mode 100644 index 0000000000..b8f80b5b12 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000105.log differ diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000107.ldb b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000107.ldb new file mode 100644 index 0000000000..33c48c0b3e Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000107.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/CURRENT b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/CURRENT new file mode 100644 index 0000000000..abdfdfe276 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/CURRENT @@ -0,0 +1 @@ +MANIFEST-000106 diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/CURRENT.bak b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/CURRENT.bak new file mode 100644 index 0000000000..e333c89b48 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000102 diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/LOCK b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/LOCK new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/LOG b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/LOG new file mode 100644 index 0000000000..f9e576d734 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/LOG @@ -0,0 +1,458 @@ +=============== Jun 4, 2024 (UTC) =============== +11:02:17.167192 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:02:17.169057 db@open opening +11:02:17.169205 version@stat F·[] S·0B[] Sc·[] +11:02:17.169819 db@janitor F·2 G·0 +11:02:17.169829 db@open done T·763.83µs +11:20:27.051134 db@close closing +11:20:27.051182 db@close done T·46.48µs +=============== Jun 6, 2024 (UTC) =============== +05:23:17.029456 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:23:17.029539 version@stat F·[] S·0B[] Sc·[] +05:23:17.029549 db@open opening +05:23:17.029574 journal@recovery F·1 +05:23:17.029663 journal@recovery recovering @1 +05:23:17.031961 memdb@flush created L0@2 N·1302 S·207KiB "BH:..c5c,v363":"blo..ore,v6" +05:23:17.032081 version@stat F·[1] S·207KiB[207KiB] Sc·[0.25] +05:23:17.034612 db@janitor F·3 G·0 +05:23:17.034624 db@open done T·5.07141ms +05:43:54.555408 db@close closing +05:43:54.555457 db@close done T·47.84µs +=============== Jun 10, 2024 (UTC) =============== +10:09:48.994026 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:09:48.994092 version@stat F·[1] S·207KiB[207KiB] Sc·[0.25] +10:09:48.994103 db@open opening +10:09:48.994131 journal@recovery F·1 +10:09:48.994739 journal@recovery recovering @3 +10:09:48.996990 memdb@flush created L0@5 N·1482 S·230KiB "BH:..100,v2722":"blo..ore,v1309" +10:09:48.998841 version@stat F·[2] S·438KiB[438KiB] Sc·[0.50] +10:09:49.001475 db@janitor F·4 G·0 +10:09:49.001485 db@open done T·7.37854ms +10:19:38.331932 db@close closing +10:19:38.331979 db@close done T·46.88µs +=============== Jun 10, 2024 (UTC) =============== +10:19:47.837374 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:19:47.837488 version@stat F·[2] S·438KiB[438KiB] Sc·[0.50] +10:19:47.837500 db@open opening +10:19:47.837533 journal@recovery F·1 +10:19:47.839083 journal@recovery recovering @6 +10:19:47.840597 memdb@flush created L0@8 N·702 S·108KiB "BH:..92c,v3053":"blo..ore,v2792" +10:19:47.842733 version@stat F·[3] S·547KiB[547KiB] Sc·[0.75] +10:19:47.845431 db@janitor F·5 G·0 +10:19:47.845458 db@open done T·7.953897ms +=============== Jun 10, 2024 (UTC) =============== +10:20:11.689661 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:20:11.689755 version@stat F·[3] S·547KiB[547KiB] Sc·[0.75] +10:20:11.689767 db@open opening +10:20:11.689805 journal@recovery F·1 +10:20:11.691702 journal@recovery recovering @9 +10:20:11.693746 version@stat F·[3] S·547KiB[547KiB] Sc·[0.75] +10:20:11.696414 db@janitor F·5 G·0 +10:20:11.696425 db@open done T·6.652796ms +10:23:04.294984 db@close closing +10:23:04.295033 db@close done T·49.151µs +=============== Jun 10, 2024 (UTC) =============== +10:23:28.239002 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:23:28.239174 version@stat F·[3] S·547KiB[547KiB] Sc·[0.75] +10:23:28.239203 db@open opening +10:23:28.239244 journal@recovery F·1 +10:23:28.241209 journal@recovery recovering @11 +10:23:28.242267 memdb@flush created L0@13 N·204 S·32KiB "BH:..7ed,v3636":"blo..ore,v3495" +10:23:28.242434 version@stat F·[4] S·579KiB[579KiB] Sc·[1.00] +10:23:28.245238 db@janitor F·6 G·0 +10:23:28.245267 db@open done T·6.058471ms +10:23:28.245334 table@compaction L0·4 -> L1·0 S·579KiB Q·3694 +10:23:28.250870 table@build created L1@16 N·3076 S·577KiB "BH:..c5c,v363":"blo..ore,v3693" +10:23:28.250893 version@stat F·[0 1] S·577KiB[0B 577KiB] Sc·[0.00 0.01] +10:23:28.252525 table@compaction committed F-3 S-1KiB Ke·0 D·614 T·7.16534ms +10:23:28.252612 table@remove removed @8 +10:23:28.252686 table@remove removed @5 +10:23:28.252750 table@remove removed @2 +10:26:58.662937 db@close closing +10:26:58.662982 db@close done T·44.621µs +=============== Jun 10, 2024 (UTC) =============== +10:27:08.413936 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:27:08.414062 version@stat F·[0 1] S·577KiB[0B 577KiB] Sc·[0.00 0.01] +10:27:08.414073 db@open opening +10:27:08.414112 journal@recovery F·1 +10:27:08.416106 journal@recovery recovering @14 +10:27:08.417170 memdb@flush created L0@17 N·252 S·39KiB "BH:..892,v3853":"blo..ore,v3700" +10:27:08.417318 version@stat F·[1 1] S·617KiB[39KiB 577KiB] Sc·[0.25 0.01] +10:27:08.419976 db@janitor F·5 G·1 +10:27:08.419983 db@janitor removing table-13 +10:27:08.420037 db@open done T·5.95856ms +10:28:46.710119 db@close closing +10:28:46.710164 db@close done T·43.77µs +=============== Jun 10, 2024 (UTC) =============== +10:30:28.027998 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:30:28.028106 version@stat F·[1 1] S·617KiB[39KiB 577KiB] Sc·[0.25 0.01] +10:30:28.028124 db@open opening +10:30:28.028170 journal@recovery F·1 +10:30:28.029934 journal@recovery recovering @18 +10:30:28.030846 memdb@flush created L0@20 N·114 S·18KiB "BH:..399,v3950":"blo..ore,v3953" +10:30:28.032879 version@stat F·[2 1] S·635KiB[58KiB 577KiB] Sc·[0.50 0.01] +10:30:28.035574 db@janitor F·5 G·0 +10:30:28.035586 db@open done T·7.456743ms +10:34:40.140392 db@close closing +10:34:40.140456 db@close done T·63.16µs +=============== Jun 10, 2024 (UTC) =============== +11:11:44.605592 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:11:44.605707 version@stat F·[2 1] S·635KiB[58KiB 577KiB] Sc·[0.50 0.01] +11:11:44.605723 db@open opening +11:11:44.605775 journal@recovery F·1 +11:11:44.607629 journal@recovery recovering @21 +11:11:44.608741 memdb@flush created L0@23 N·300 S·46KiB "BH:..33c,v4293":"blo..ore,v4068" +11:11:44.610678 version@stat F·[3 1] S·682KiB[104KiB 577KiB] Sc·[0.75 0.01] +11:11:44.613493 db@janitor F·6 G·0 +11:11:44.613509 db@open done T·7.777625ms +11:13:21.544611 table@compaction L0·3 -> L1·1 S·682KiB Q·4477 +11:13:21.549117 table@build created L1@26 N·3631 S·681KiB "BH:..c5c,v363":"blo..ore,v4362" +11:13:21.549139 version@stat F·[0 1] S·681KiB[0B 681KiB] Sc·[0.00 0.01] +11:13:21.549722 table@compaction committed F-3 S-927B Ke·0 D·111 T·5.090632ms +11:13:21.549781 table@remove removed @20 +11:13:21.549819 table@remove removed @17 +11:13:21.549946 table@remove removed @16 +11:13:46.210519 db@close closing +11:13:46.210605 db@close done T·82.181µs +=============== Jun 10, 2024 (UTC) =============== +11:15:00.395160 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:15:00.395289 version@stat F·[0 1] S·681KiB[0B 681KiB] Sc·[0.00 0.01] +11:15:00.395304 db@open opening +11:15:00.395335 journal@recovery F·1 +11:15:00.397149 journal@recovery recovering @24 +11:15:00.398119 memdb@flush created L0@27 N·144 S·28KiB "BH:..02c,v4498":"blo..ore,v4369" +11:15:00.398262 version@stat F·[1 1] S·710KiB[28KiB 681KiB] Sc·[0.25 0.01] +11:15:00.400949 db@janitor F·5 G·1 +11:15:00.400956 db@janitor removing table-23 +11:15:00.401012 db@open done T·5.704517ms +=============== Jun 10, 2024 (UTC) =============== +11:21:08.218334 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:21:08.218427 version@stat F·[1 1] S·710KiB[28KiB 681KiB] Sc·[0.25 0.01] +11:21:08.218443 db@open opening +11:21:08.218475 journal@recovery F·1 +11:21:08.220368 journal@recovery recovering @28 +11:21:08.222289 version@stat F·[1 1] S·710KiB[28KiB 681KiB] Sc·[0.25 0.01] +11:21:08.224916 db@janitor F·4 G·0 +11:21:08.224931 db@open done T·6.481994ms +=============== Jun 10, 2024 (UTC) =============== +11:22:28.286946 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:22:28.287033 version@stat F·[1 1] S·710KiB[28KiB 681KiB] Sc·[0.25 0.01] +11:22:28.287043 db@open opening +11:22:28.287071 journal@recovery F·1 +11:22:28.287370 journal@recovery recovering @30 +11:22:28.288112 memdb@flush created L0@32 N·6 S·963B "BH:..686,v4511":"blo..ore,v4514" +11:22:28.288229 version@stat F·[2 1] S·711KiB[29KiB 681KiB] Sc·[0.50 0.01] +11:22:28.290768 db@janitor F·5 G·0 +11:22:28.290778 db@open done T·3.732711ms +11:22:57.093632 db@close closing +11:22:57.093684 db@close done T·51.87µs +=============== Jun 10, 2024 (UTC) =============== +11:23:15.251147 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:23:15.251266 version@stat F·[2 1] S·711KiB[29KiB 681KiB] Sc·[0.50 0.01] +11:23:15.251279 db@open opening +11:23:15.251309 journal@recovery F·1 +11:23:15.251613 journal@recovery recovering @33 +11:23:15.252445 memdb@flush created L0@35 N·30 S·3KiB "BH:..674,v4524":"blo..ore,v4521" +11:23:15.252583 version@stat F·[3 1] S·715KiB[33KiB 681KiB] Sc·[0.75 0.01] +11:23:15.255175 db@janitor F·6 G·0 +11:23:15.255196 db@open done T·3.913293ms +11:28:46.931016 db@close closing +11:28:46.931081 db@close done T·64.521µs +=============== Jun 10, 2024 (UTC) =============== +11:29:08.621812 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:29:08.621915 version@stat F·[3 1] S·715KiB[33KiB 681KiB] Sc·[0.75 0.01] +11:29:08.621928 db@open opening +11:29:08.621962 journal@recovery F·1 +11:29:08.623929 journal@recovery recovering @36 +11:29:08.625168 memdb@flush created L0@38 N·396 S·61KiB "BH:..882,v4657":"blo..ore,v4552" +11:29:08.625451 version@stat F·[4 1] S·776KiB[94KiB 681KiB] Sc·[1.00 0.01] +11:29:08.628275 db@janitor F·7 G·0 +11:29:08.628290 db@open done T·6.357054ms +11:29:08.628317 table@compaction L0·4 -> L1·1 S·776KiB Q·4943 +11:29:08.634076 table@build created L1@41 N·4111 S·776KiB "BH:..c5c,v363":"blo..ore,v4942" +11:29:08.634098 version@stat F·[0 1] S·776KiB[0B 776KiB] Sc·[0.00 0.01] +11:29:08.635339 table@compaction committed F-4 S+187B Ke·0 D·96 T·7.003189ms +11:29:08.635404 table@remove removed @35 +11:29:08.635436 table@remove removed @32 +11:29:08.635473 table@remove removed @27 +11:29:08.635616 table@remove removed @26 +11:30:37.043712 db@close closing +11:30:37.043766 db@close done T·53.58µs +=============== Jun 10, 2024 (UTC) =============== +11:30:42.302109 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:42.302249 version@stat F·[0 1] S·776KiB[0B 776KiB] Sc·[0.00 0.01] +11:30:42.302268 db@open opening +11:30:42.302314 journal@recovery F·1 +11:30:42.302608 journal@recovery recovering @39 +11:30:42.303604 memdb@flush created L0@42 N·102 S·16KiB "BH:..42a,v5036":"blo..ore,v4949" +11:30:42.303790 version@stat F·[1 1] S·792KiB[16KiB 776KiB] Sc·[0.25 0.01] +11:30:42.312329 db@janitor F·5 G·1 +11:30:42.312341 db@janitor removing table-38 +11:30:42.312405 db@open done T·10.130265ms +=============== Jun 10, 2024 (UTC) =============== +11:30:55.689994 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:55.690084 version@stat F·[1 1] S·792KiB[16KiB 776KiB] Sc·[0.25 0.01] +11:30:55.690098 db@open opening +11:30:55.690130 journal@recovery F·1 +11:30:55.690436 journal@recovery recovering @43 +11:30:55.691219 memdb@flush created L0@45 N·6 S·958B "BH:..90d,v5049":"blo..ore,v5052" +11:30:55.693168 version@stat F·[2 1] S·793KiB[17KiB 776KiB] Sc·[0.50 0.01] +11:30:55.695733 db@janitor F·5 G·0 +11:30:55.695744 db@open done T·5.641957ms +11:35:56.722570 db@close closing +11:35:56.722627 db@close done T·56.51µs +=============== Jun 10, 2024 (UTC) =============== +11:36:36.076466 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:36:36.076569 version@stat F·[2 1] S·793KiB[17KiB 776KiB] Sc·[0.50 0.01] +11:36:36.076587 db@open opening +11:36:36.076617 journal@recovery F·1 +11:36:36.078605 journal@recovery recovering @46 +11:36:36.079833 memdb@flush created L0@48 N·360 S·56KiB "BH:..7f2,v5146":"blo..ore,v5059" +11:36:36.080080 version@stat F·[3 1] S·850KiB[73KiB 776KiB] Sc·[0.75 0.01] +11:36:36.082842 db@janitor F·6 G·0 +11:36:36.082856 db@open done T·6.265422ms +11:40:48.235708 db@close closing +11:40:48.235769 db@close done T·60.051µs +=============== Jun 10, 2024 (UTC) =============== +12:01:04.961874 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:01:04.961988 version@stat F·[3 1] S·850KiB[73KiB 776KiB] Sc·[0.75 0.01] +12:01:04.962005 db@open opening +12:01:04.962052 journal@recovery F·1 +12:01:04.962165 journal@recovery recovering @49 +12:01:04.963449 memdb@flush created L0@51 N·300 S·46KiB "BH:..60a,v5591":"blo..ore,v5420" +12:01:04.963613 version@stat F·[4 1] S·896KiB[120KiB 776KiB] Sc·[1.00 0.01] +12:01:04.966322 db@janitor F·7 G·0 +12:01:04.966337 db@open done T·4.326219ms +12:01:04.966376 table@compaction L0·4 -> L1·1 S·896KiB Q·5715 +12:01:04.973198 table@build created L1@54 N·4751 S·897KiB "BH:..c5c,v363":"blo..ore,v5714" +12:01:04.973224 version@stat F·[0 1] S·897KiB[0B 897KiB] Sc·[0.00 0.01] +12:01:04.974775 table@compaction committed F-4 S+406B Ke·0 D·128 T·8.360764ms +12:01:04.974845 table@remove removed @48 +12:01:04.974878 table@remove removed @45 +12:01:04.974912 table@remove removed @42 +12:01:04.975077 table@remove removed @41 +12:01:23.716813 db@close closing +12:01:23.716866 db@close done T·52.111µs +=============== Jun 10, 2024 (UTC) =============== +12:02:59.952861 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:02:59.952973 version@stat F·[0 1] S·897KiB[0B 897KiB] Sc·[0.00 0.01] +12:02:59.952986 db@open opening +12:02:59.953018 journal@recovery F·1 +12:02:59.954882 journal@recovery recovering @52 +12:02:59.955700 memdb@flush created L0@55 N·18 S·2KiB "BH:..d21,v5718":"blo..ore,v5721" +12:02:59.955835 version@stat F·[1 1] S·899KiB[2KiB 897KiB] Sc·[0.25 0.01] +12:02:59.958450 db@janitor F·5 G·1 +12:02:59.958461 db@janitor removing table-51 +12:02:59.958507 db@open done T·5.515799ms +=============== Jun 10, 2024 (UTC) =============== +12:03:19.612706 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:19.612822 version@stat F·[1 1] S·899KiB[2KiB 897KiB] Sc·[0.25 0.01] +12:03:19.612837 db@open opening +12:03:19.612868 journal@recovery F·1 +12:03:19.614715 journal@recovery recovering @56 +12:03:19.615550 memdb@flush created L0@58 N·12 S·1KiB "BH:..fdd,v5737":"blo..ore,v5740" +12:03:19.615710 version@stat F·[2 1] S·900KiB[3KiB 897KiB] Sc·[0.50 0.01] +12:03:19.618942 db@janitor F·5 G·0 +12:03:19.618957 db@open done T·6.114524ms +=============== Jun 10, 2024 (UTC) =============== +12:04:23.542876 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:04:23.543017 version@stat F·[2 1] S·900KiB[3KiB 897KiB] Sc·[0.50 0.01] +12:04:23.543037 db@open opening +12:04:23.543093 journal@recovery F·1 +12:04:23.543219 journal@recovery recovering @59 +12:04:23.544112 memdb@flush created L0@61 N·6 S·958B "BH:..ea0,v5750":"blo..ore,v5753" +12:04:23.544448 version@stat F·[3 1] S·901KiB[4KiB 897KiB] Sc·[0.75 0.01] +12:04:23.547433 db@janitor F·6 G·0 +12:04:23.547453 db@open done T·4.409409ms +=============== Jun 10, 2024 (UTC) =============== +12:07:46.666287 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:07:46.666385 version@stat F·[3 1] S·901KiB[4KiB 897KiB] Sc·[0.75 0.01] +12:07:46.666397 db@open opening +12:07:46.666428 journal@recovery F·1 +12:07:46.666516 journal@recovery recovering @62 +12:07:46.666654 version@stat F·[3 1] S·901KiB[4KiB 897KiB] Sc·[0.75 0.01] +12:07:46.670100 db@janitor F·6 G·0 +12:07:46.670122 db@open done T·3.720013ms +=============== Jun 10, 2024 (UTC) =============== +12:08:02.061159 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:08:02.061341 version@stat F·[3 1] S·901KiB[4KiB 897KiB] Sc·[0.75 0.01] +12:08:02.061358 db@open opening +12:08:02.061395 journal@recovery F·1 +12:08:02.063219 journal@recovery recovering @64 +12:08:02.063403 version@stat F·[3 1] S·901KiB[4KiB 897KiB] Sc·[0.75 0.01] +12:08:02.066748 db@janitor F·6 G·0 +12:08:02.066758 db@open done T·5.394877ms +=============== Jun 10, 2024 (UTC) =============== +12:27:21.417846 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:27:21.418053 version@stat F·[3 1] S·901KiB[4KiB 897KiB] Sc·[0.75 0.01] +12:27:21.418074 db@open opening +12:27:21.418132 journal@recovery F·1 +12:27:21.420620 journal@recovery recovering @66 +12:27:21.420876 version@stat F·[3 1] S·901KiB[4KiB 897KiB] Sc·[0.75 0.01] +12:27:21.423677 db@janitor F·6 G·0 +12:27:21.423712 db@open done T·5.630459ms +=============== Jun 10, 2024 (UTC) =============== +12:56:43.777795 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:56:43.777986 version@stat F·[3 1] S·901KiB[4KiB 897KiB] Sc·[0.75 0.01] +12:56:43.778006 db@open opening +12:56:43.778062 journal@recovery F·1 +12:56:43.778197 journal@recovery recovering @68 +12:56:43.779023 memdb@flush created L0@70 N·12 S·1KiB "BH:..7fc,v5763":"blo..ore,v5760" +12:56:43.779334 version@stat F·[4 1] S·903KiB[6KiB 897KiB] Sc·[1.00 0.01] +12:56:43.782135 db@janitor F·7 G·0 +12:56:43.782149 db@open done T·4.137736ms +12:56:43.782180 table@compaction L0·4 -> L1·1 S·903KiB Q·5767 +12:56:43.791584 table@build created L1@73 N·4791 S·903KiB "BH:..c5c,v363":"blo..ore,v5766" +12:56:43.791609 version@stat F·[0 1] S·903KiB[0B 903KiB] Sc·[0.00 0.01] +12:56:43.793192 table@compaction committed F-4 S-54B Ke·0 D·8 T·10.985056ms +12:56:43.793334 table@remove removed @61 +12:56:43.793377 table@remove removed @58 +12:56:43.793418 table@remove removed @55 +12:56:43.793662 table@remove removed @54 +=============== Jun 10, 2024 (UTC) =============== +12:57:38.349938 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:57:38.350097 version@stat F·[0 1] S·903KiB[0B 903KiB] Sc·[0.00 0.01] +12:57:38.350118 db@open opening +12:57:38.350163 journal@recovery F·1 +12:57:38.350486 journal@recovery recovering @71 +12:57:38.350653 version@stat F·[0 1] S·903KiB[0B 903KiB] Sc·[0.00 0.01] +12:57:38.353367 db@janitor F·4 G·1 +12:57:38.353378 db@janitor removing table-70 +12:57:38.353420 db@open done T·3.295768ms +=============== Jun 10, 2024 (UTC) =============== +12:58:01.444508 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:01.444615 version@stat F·[0 1] S·903KiB[0B 903KiB] Sc·[0.00 0.01] +12:58:01.444630 db@open opening +12:58:01.444660 journal@recovery F·1 +12:58:01.446613 journal@recovery recovering @74 +12:58:01.447398 memdb@flush created L0@76 N·6 S·958B "BH:..6b7,v5770":"blo..ore,v5773" +12:58:01.447667 version@stat F·[1 1] S·903KiB[958B 903KiB] Sc·[0.25 0.01] +12:58:01.450613 db@janitor F·4 G·0 +12:58:01.450627 db@open done T·5.992352ms +=============== Jun 10, 2024 (UTC) =============== +12:58:48.967138 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:48.967225 version@stat F·[1 1] S·903KiB[958B 903KiB] Sc·[0.25 0.01] +12:58:48.967239 db@open opening +12:58:48.967269 journal@recovery F·1 +12:58:48.967356 journal@recovery recovering @77 +12:58:48.968077 memdb@flush created L0@79 N·12 S·1KiB "BH:..45e,v5783":"blo..ore,v5780" +12:58:48.968206 version@stat F·[2 1] S·905KiB[2KiB 903KiB] Sc·[0.50 0.01] +12:58:48.972449 db@janitor F·5 G·0 +12:58:48.972466 db@open done T·5.222185ms +=============== Jun 10, 2024 (UTC) =============== +12:59:05.212420 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:05.212508 version@stat F·[2 1] S·905KiB[2KiB 903KiB] Sc·[0.50 0.01] +12:59:05.212521 db@open opening +12:59:05.212553 journal@recovery F·1 +12:59:05.212641 journal@recovery recovering @80 +12:59:05.212778 version@stat F·[2 1] S·905KiB[2KiB 903KiB] Sc·[0.50 0.01] +12:59:05.216088 db@janitor F·5 G·0 +12:59:05.216102 db@open done T·3.574251ms +=============== Jun 10, 2024 (UTC) =============== +12:59:32.394691 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:32.394801 version@stat F·[2 1] S·905KiB[2KiB 903KiB] Sc·[0.50 0.01] +12:59:32.394814 db@open opening +12:59:32.394845 journal@recovery F·1 +12:59:32.394945 journal@recovery recovering @82 +12:59:32.395725 memdb@flush created L0@84 N·6 S·963B "BH:..f72,v5790":"blo..ore,v5793" +12:59:32.395975 version@stat F·[3 1] S·906KiB[3KiB 903KiB] Sc·[0.75 0.01] +12:59:32.398691 db@janitor F·6 G·0 +12:59:32.398707 db@open done T·3.888023ms +=============== Jul 3, 2024 (UTC) =============== +17:52:58.314317 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:52:58.314484 version@stat F·[3 1] S·906KiB[3KiB 903KiB] Sc·[0.75 0.01] +17:52:58.314500 db@open opening +17:52:58.314539 journal@recovery F·1 +17:52:58.316433 journal@recovery recovering @85 +17:52:58.316614 version@stat F·[3 1] S·906KiB[3KiB 903KiB] Sc·[0.75 0.01] +17:52:58.319264 db@janitor F·6 G·0 +17:52:58.319291 db@open done T·4.786289ms +17:58:44.101809 db@close closing +17:58:44.101855 db@close done T·46.011µs +=============== Jul 3, 2024 (UTC) =============== +17:59:01.692437 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:59:01.692555 version@stat F·[3 1] S·906KiB[3KiB 903KiB] Sc·[0.75 0.01] +17:59:01.692569 db@open opening +17:59:01.692603 journal@recovery F·1 +17:59:01.694560 journal@recovery recovering @87 +17:59:01.695928 memdb@flush created L0@89 N·414 S·71KiB "BH:..1bf,v6121":"blo..ore,v5800" +17:59:01.696078 version@stat F·[4 1] S·977KiB[74KiB 903KiB] Sc·[1.00 0.01] +17:59:01.698791 db@janitor F·7 G·0 +17:59:01.698808 db@open done T·6.231022ms +17:59:01.698849 table@compaction L0·4 -> L1·1 S·977KiB Q·6209 +17:59:01.706606 table@build created L1@92 N·5156 S·979KiB "BH:..c5c,v363":"blo..ore,v6208" +17:59:01.706633 version@stat F·[0 1] S·979KiB[0B 979KiB] Sc·[0.00 0.01] +17:59:01.707731 table@compaction committed F-4 S+1KiB Ke·0 D·73 T·8.851353ms +17:59:01.707829 table@remove removed @84 +17:59:01.707863 table@remove removed @79 +17:59:01.707892 table@remove removed @76 +17:59:01.708096 table@remove removed @73 +17:59:27.006678 db@close closing +17:59:27.006727 db@close done T·48.84µs +=============== Oct 17, 2024 (UTC) =============== +05:10:40.941929 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.942055 version@stat F·[0 1] S·979KiB[0B 979KiB] Sc·[0.00 0.01] +05:10:40.942068 db@open opening +05:10:40.942101 journal@recovery F·1 +05:10:40.943919 journal@recovery recovering @90 +05:10:40.944706 memdb@flush created L0@93 N·30 S·3KiB "BH:..e6d,v6218":"blo..ore,v6215" +05:10:40.945097 version@stat F·[1 1] S·983KiB[3KiB 979KiB] Sc·[0.25 0.01] +05:10:40.947901 db@janitor F·5 G·1 +05:10:40.947908 db@janitor removing table-89 +05:10:40.947956 db@open done T·5.883755ms +05:11:11.986457 table@compaction L0·1 -> L1·1 S·983KiB Q·6276 +05:11:11.992423 table@build created L1@96 N·5181 S·982KiB "BH:..c5c,v363":"blo..ore,v6239" +05:11:11.992451 version@stat F·[0 1] S·982KiB[0B 982KiB] Sc·[0.00 0.01] +05:11:11.993034 table@compaction committed F-1 S-280B Ke·0 D·5 T·6.5542ms +05:11:11.993266 table@remove removed @92 +05:22:45.688799 db@close closing +05:22:45.688852 db@close done T·52.32µs +=============== Oct 17, 2024 (UTC) =============== +06:44:58.731477 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.731572 version@stat F·[0 1] S·982KiB[0B 982KiB] Sc·[0.00 0.01] +06:44:58.731582 db@open opening +06:44:58.731608 journal@recovery F·1 +06:44:58.733590 journal@recovery recovering @94 +06:44:58.741468 memdb@flush created L0@97 N·864 S·142KiB "BH:..91c,v6735":"blo..ore,v6246" +06:44:58.741742 version@stat F·[1 1] S·1MiB[142KiB 982KiB] Sc·[0.25 0.01] +06:44:58.757548 db@janitor F·5 G·1 +06:44:58.757554 db@janitor removing table-93 +06:44:58.757586 db@open done T·26.001494ms +06:45:36.822661 db@close closing +06:45:36.822719 db@close done T·57.27µs +=============== Oct 17, 2024 (UTC) =============== +06:45:46.666531 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.666665 version@stat F·[1 1] S·1MiB[142KiB 982KiB] Sc·[0.25 0.01] +06:45:46.666678 db@open opening +06:45:46.666711 journal@recovery F·1 +06:45:46.668662 journal@recovery recovering @98 +06:45:46.669474 memdb@flush created L0@100 N·42 S·10KiB "BH:..b99,v7144":"blo..ore,v7111" +06:45:46.669607 version@stat F·[2 1] S·1MiB[152KiB 982KiB] Sc·[0.50 0.01] +06:45:46.672153 db@janitor F·5 G·0 +06:45:46.672180 db@open done T·5.497737ms +06:46:16.625229 table@compaction L0·2 -> L1·1 S·1MiB Q·7178 +06:46:16.632031 table@build created L1@103 N·5936 S·1MiB "BH:..c5c,v363":"blo..ore,v7147" +06:46:16.632059 version@stat F·[0 1] S·1MiB[0B 1MiB] Sc·[0.00 0.01] +06:46:16.632639 table@compaction committed F-2 S+122B Ke·0 D·151 T·7.390783ms +06:46:16.632728 table@remove removed @97 +06:46:16.632918 table@remove removed @96 +06:47:06.119564 db@close closing +06:47:06.119632 db@close done T·68.141µs +=============== Oct 17, 2024 (UTC) =============== +06:47:26.289119 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.289213 version@stat F·[0 1] S·1MiB[0B 1MiB] Sc·[0.00 0.01] +06:47:26.289224 db@open opening +06:47:26.289255 journal@recovery F·1 +06:47:26.291626 journal@recovery recovering @101 +06:47:26.293004 memdb@flush created L0@104 N·90 S·14KiB "BH:..022,v7211":"blo..ore,v7154" +06:47:26.293131 version@stat F·[1 1] S·1MiB[14KiB 1MiB] Sc·[0.25 0.01] +06:47:26.295912 db@janitor F·5 G·1 +06:47:26.295920 db@janitor removing table-100 +06:47:26.295955 db@open done T·6.727058ms +06:47:51.340007 table@compaction L0·1 -> L1·1 S·1MiB Q·7263 +06:47:51.346786 table@build created L1@107 N·6011 S·1MiB "BH:..c5c,v363":"blo..ore,v7238" +06:47:51.346809 version@stat F·[0 1] S·1MiB[0B 1MiB] Sc·[0.00 0.01] +06:47:51.348343 table@compaction committed F-1 S-97B Ke·0 D·15 T·8.315581ms +06:47:51.348639 table@remove removed @103 +06:47:53.050412 db@close closing +06:47:53.050476 db@close done T·62.691µs diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/MANIFEST-000106 b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/MANIFEST-000106 new file mode 100644 index 0000000000..3959a2ff27 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/MANIFEST-000106 differ diff --git a/.docker/container-state/nucleus-testnet-data/data/cs.wal/wal b/.docker/container-state/nucleus-testnet-data/data/cs.wal/wal new file mode 100644 index 0000000000..a0de7456d1 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/cs.wal/wal differ diff --git a/.docker/container-state/nucleus-testnet-data/data/evidence.db/000070.log b/.docker/container-state/nucleus-testnet-data/data/evidence.db/000070.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/nucleus-testnet-data/data/evidence.db/CURRENT b/.docker/container-state/nucleus-testnet-data/data/evidence.db/CURRENT new file mode 100644 index 0000000000..be93edb695 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/evidence.db/CURRENT @@ -0,0 +1 @@ +MANIFEST-000071 diff --git a/.docker/container-state/nucleus-testnet-data/data/evidence.db/CURRENT.bak b/.docker/container-state/nucleus-testnet-data/data/evidence.db/CURRENT.bak new file mode 100644 index 0000000000..5893b8f83b --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/evidence.db/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000069 diff --git a/.docker/container-state/nucleus-testnet-data/data/evidence.db/LOCK b/.docker/container-state/nucleus-testnet-data/data/evidence.db/LOCK new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/nucleus-testnet-data/data/evidence.db/LOG b/.docker/container-state/nucleus-testnet-data/data/evidence.db/LOG new file mode 100644 index 0000000000..603afd6c50 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/evidence.db/LOG @@ -0,0 +1,321 @@ +=============== Jun 4, 2024 (UTC) =============== +11:02:17.187082 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:02:17.188914 db@open opening +11:02:17.189040 version@stat F·[] S·0B[] Sc·[] +11:02:17.189650 db@janitor F·2 G·0 +11:02:17.189659 db@open done T·738.62µs +=============== Jun 6, 2024 (UTC) =============== +05:23:17.053111 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:23:17.053173 version@stat F·[] S·0B[] Sc·[] +05:23:17.053182 db@open opening +05:23:17.053205 journal@recovery F·1 +05:23:17.053281 journal@recovery recovering @1 +05:23:17.053412 version@stat F·[] S·0B[] Sc·[] +05:23:17.055873 db@janitor F·2 G·0 +05:23:17.055889 db@open done T·2.702971ms +=============== Jun 10, 2024 (UTC) =============== +10:09:49.023169 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:09:49.023239 version@stat F·[] S·0B[] Sc·[] +10:09:49.023249 db@open opening +10:09:49.023275 journal@recovery F·1 +10:09:49.023363 journal@recovery recovering @2 +10:09:49.023493 version@stat F·[] S·0B[] Sc·[] +10:09:49.026195 db@janitor F·2 G·0 +10:09:49.026206 db@open done T·2.953584ms +=============== Jun 10, 2024 (UTC) =============== +10:19:47.860561 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:19:47.860624 version@stat F·[] S·0B[] Sc·[] +10:19:47.860635 db@open opening +10:19:47.860667 journal@recovery F·1 +10:19:47.860754 journal@recovery recovering @4 +10:19:47.860899 version@stat F·[] S·0B[] Sc·[] +10:19:47.863633 db@janitor F·2 G·0 +10:19:47.863647 db@open done T·3.008235ms +=============== Jun 10, 2024 (UTC) =============== +10:20:11.703815 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:20:11.703905 version@stat F·[] S·0B[] Sc·[] +10:20:11.703917 db@open opening +10:20:11.703945 journal@recovery F·1 +10:20:11.705878 journal@recovery recovering @6 +10:20:11.706028 version@stat F·[] S·0B[] Sc·[] +10:20:11.708583 db@janitor F·2 G·0 +10:20:11.708596 db@open done T·4.675399ms +=============== Jun 10, 2024 (UTC) =============== +10:23:28.256992 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:23:28.257065 version@stat F·[] S·0B[] Sc·[] +10:23:28.257076 db@open opening +10:23:28.257105 journal@recovery F·1 +10:23:28.257199 journal@recovery recovering @8 +10:23:28.257354 version@stat F·[] S·0B[] Sc·[] +10:23:28.259997 db@janitor F·2 G·0 +10:23:28.260016 db@open done T·2.934545ms +=============== Jun 10, 2024 (UTC) =============== +10:27:08.430669 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:27:08.430731 version@stat F·[] S·0B[] Sc·[] +10:27:08.430741 db@open opening +10:27:08.430766 journal@recovery F·1 +10:27:08.430848 journal@recovery recovering @10 +10:27:08.430991 version@stat F·[] S·0B[] Sc·[] +10:27:08.433790 db@janitor F·2 G·0 +10:27:08.433810 db@open done T·3.064686ms +=============== Jun 10, 2024 (UTC) =============== +10:30:28.047713 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:30:28.047802 version@stat F·[] S·0B[] Sc·[] +10:30:28.047815 db@open opening +10:30:28.047854 journal@recovery F·1 +10:30:28.047961 journal@recovery recovering @12 +10:30:28.048120 version@stat F·[] S·0B[] Sc·[] +10:30:28.050790 db@janitor F·2 G·0 +10:30:28.050801 db@open done T·2.981765ms +=============== Jun 10, 2024 (UTC) =============== +11:11:44.624807 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:11:44.624892 version@stat F·[] S·0B[] Sc·[] +11:11:44.624907 db@open opening +11:11:44.624945 journal@recovery F·1 +11:11:44.625058 journal@recovery recovering @14 +11:11:44.625230 version@stat F·[] S·0B[] Sc·[] +11:11:44.627948 db@janitor F·2 G·0 +11:11:44.627959 db@open done T·3.047036ms +=============== Jun 10, 2024 (UTC) =============== +11:15:00.412843 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:15:00.412930 version@stat F·[] S·0B[] Sc·[] +11:15:00.412942 db@open opening +11:15:00.412971 journal@recovery F·1 +11:15:00.413060 journal@recovery recovering @16 +11:15:00.413215 version@stat F·[] S·0B[] Sc·[] +11:15:00.415839 db@janitor F·2 G·0 +11:15:00.415851 db@open done T·2.904874ms +=============== Jun 10, 2024 (UTC) =============== +11:21:08.234535 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:21:08.234617 version@stat F·[] S·0B[] Sc·[] +11:21:08.234632 db@open opening +11:21:08.234671 journal@recovery F·1 +11:21:08.234779 journal@recovery recovering @18 +11:21:08.234941 version@stat F·[] S·0B[] Sc·[] +11:21:08.237567 db@janitor F·2 G·0 +11:21:08.237584 db@open done T·2.947235ms +=============== Jun 10, 2024 (UTC) =============== +11:22:28.299878 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:22:28.299971 version@stat F·[] S·0B[] Sc·[] +11:22:28.299984 db@open opening +11:22:28.300023 journal@recovery F·1 +11:22:28.302041 journal@recovery recovering @20 +11:22:28.302197 version@stat F·[] S·0B[] Sc·[] +11:22:28.304730 db@janitor F·2 G·0 +11:22:28.304742 db@open done T·4.754419ms +=============== Jun 10, 2024 (UTC) =============== +11:23:15.265595 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:23:15.265664 version@stat F·[] S·0B[] Sc·[] +11:23:15.265674 db@open opening +11:23:15.265698 journal@recovery F·1 +11:23:15.265775 journal@recovery recovering @22 +11:23:15.265902 version@stat F·[] S·0B[] Sc·[] +11:23:15.268365 db@janitor F·2 G·0 +11:23:15.268375 db@open done T·2.697262ms +=============== Jun 10, 2024 (UTC) =============== +11:29:08.639966 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:29:08.640035 version@stat F·[] S·0B[] Sc·[] +11:29:08.640049 db@open opening +11:29:08.640077 journal@recovery F·1 +11:29:08.640316 journal@recovery recovering @24 +11:29:08.640704 version@stat F·[] S·0B[] Sc·[] +11:29:08.643423 db@janitor F·2 G·0 +11:29:08.643437 db@open done T·3.382309ms +=============== Jun 10, 2024 (UTC) =============== +11:30:42.323978 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:42.324044 version@stat F·[] S·0B[] Sc·[] +11:30:42.324055 db@open opening +11:30:42.324083 journal@recovery F·1 +11:30:42.324168 journal@recovery recovering @26 +11:30:42.324302 version@stat F·[] S·0B[] Sc·[] +11:30:42.326923 db@janitor F·2 G·0 +11:30:42.326934 db@open done T·2.875554ms +=============== Jun 10, 2024 (UTC) =============== +11:30:55.706903 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:55.706987 version@stat F·[] S·0B[] Sc·[] +11:30:55.707004 db@open opening +11:30:55.707033 journal@recovery F·1 +11:30:55.707112 journal@recovery recovering @28 +11:30:55.707250 version@stat F·[] S·0B[] Sc·[] +11:30:55.709798 db@janitor F·2 G·0 +11:30:55.709811 db@open done T·2.802093ms +=============== Jun 10, 2024 (UTC) =============== +11:36:36.096066 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:36:36.096134 version@stat F·[] S·0B[] Sc·[] +11:36:36.096146 db@open opening +11:36:36.096173 journal@recovery F·1 +11:36:36.096373 journal@recovery recovering @30 +11:36:36.096813 version@stat F·[] S·0B[] Sc·[] +11:36:36.099532 db@janitor F·2 G·0 +11:36:36.099551 db@open done T·3.400128ms +=============== Jun 10, 2024 (UTC) =============== +12:01:04.980476 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:01:04.980556 version@stat F·[] S·0B[] Sc·[] +12:01:04.980571 db@open opening +12:01:04.980604 journal@recovery F·1 +12:01:04.980714 journal@recovery recovering @32 +12:01:04.980988 version@stat F·[] S·0B[] Sc·[] +12:01:04.983784 db@janitor F·2 G·0 +12:01:04.983795 db@open done T·3.218849ms +=============== Jun 10, 2024 (UTC) =============== +12:02:59.970127 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:02:59.970222 version@stat F·[] S·0B[] Sc·[] +12:02:59.970239 db@open opening +12:02:59.970280 journal@recovery F·1 +12:02:59.970391 journal@recovery recovering @34 +12:02:59.970578 version@stat F·[] S·0B[] Sc·[] +12:02:59.973860 db@janitor F·2 G·0 +12:02:59.973876 db@open done T·3.630602ms +=============== Jun 10, 2024 (UTC) =============== +12:03:19.630533 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:19.630602 version@stat F·[] S·0B[] Sc·[] +12:03:19.630614 db@open opening +12:03:19.630641 journal@recovery F·1 +12:03:19.630721 journal@recovery recovering @36 +12:03:19.630848 version@stat F·[] S·0B[] Sc·[] +12:03:19.633451 db@janitor F·2 G·0 +12:03:19.633470 db@open done T·2.852455ms +=============== Jun 10, 2024 (UTC) =============== +12:04:23.559233 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:04:23.559349 version@stat F·[] S·0B[] Sc·[] +12:04:23.559367 db@open opening +12:04:23.559411 journal@recovery F·1 +12:04:23.559671 journal@recovery recovering @38 +12:04:23.560103 version@stat F·[] S·0B[] Sc·[] +12:04:23.563554 db@janitor F·2 G·0 +12:04:23.563570 db@open done T·4.197407ms +=============== Jun 10, 2024 (UTC) =============== +12:07:46.679721 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:07:46.679789 version@stat F·[] S·0B[] Sc·[] +12:07:46.679801 db@open opening +12:07:46.679829 journal@recovery F·1 +12:07:46.679909 journal@recovery recovering @40 +12:07:46.680047 version@stat F·[] S·0B[] Sc·[] +12:07:46.682611 db@janitor F·2 G·0 +12:07:46.682621 db@open done T·2.815235ms +=============== Jun 10, 2024 (UTC) =============== +12:08:02.074814 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:08:02.074917 version@stat F·[] S·0B[] Sc·[] +12:08:02.074933 db@open opening +12:08:02.074966 journal@recovery F·1 +12:08:02.075076 journal@recovery recovering @42 +12:08:02.075231 version@stat F·[] S·0B[] Sc·[] +12:08:02.077960 db@janitor F·2 G·0 +12:08:02.077978 db@open done T·3.038487ms +=============== Jun 10, 2024 (UTC) =============== +12:27:21.431883 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:27:21.432009 version@stat F·[] S·0B[] Sc·[] +12:27:21.432023 db@open opening +12:27:21.432054 journal@recovery F·1 +12:27:21.432163 journal@recovery recovering @44 +12:27:21.432338 version@stat F·[] S·0B[] Sc·[] +12:27:21.436666 db@janitor F·2 G·0 +12:27:21.436680 db@open done T·4.651501ms +=============== Jun 10, 2024 (UTC) =============== +12:56:43.794221 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:56:43.794281 version@stat F·[] S·0B[] Sc·[] +12:56:43.794292 db@open opening +12:56:43.794322 journal@recovery F·1 +12:56:43.794573 journal@recovery recovering @46 +12:56:43.794994 version@stat F·[] S·0B[] Sc·[] +12:56:43.797799 db@janitor F·2 G·0 +12:56:43.797809 db@open done T·3.513421ms +=============== Jun 10, 2024 (UTC) =============== +12:57:38.361625 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:57:38.361689 version@stat F·[] S·0B[] Sc·[] +12:57:38.361700 db@open opening +12:57:38.361725 journal@recovery F·1 +12:57:38.363627 journal@recovery recovering @48 +12:57:38.363765 version@stat F·[] S·0B[] Sc·[] +12:57:38.366313 db@janitor F·2 G·0 +12:57:38.366324 db@open done T·4.621091ms +=============== Jun 10, 2024 (UTC) =============== +12:58:01.458920 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:01.459002 version@stat F·[] S·0B[] Sc·[] +12:58:01.459016 db@open opening +12:58:01.459053 journal@recovery F·1 +12:58:01.459168 journal@recovery recovering @50 +12:58:01.459840 version@stat F·[] S·0B[] Sc·[] +12:58:01.463073 db@janitor F·2 G·0 +12:58:01.463087 db@open done T·4.065715ms +=============== Jun 10, 2024 (UTC) =============== +12:58:48.984610 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:48.984672 version@stat F·[] S·0B[] Sc·[] +12:58:48.984685 db@open opening +12:58:48.984711 journal@recovery F·1 +12:58:48.984791 journal@recovery recovering @52 +12:58:48.984935 version@stat F·[] S·0B[] Sc·[] +12:58:48.987578 db@janitor F·2 G·0 +12:58:48.987590 db@open done T·2.900696ms +=============== Jun 10, 2024 (UTC) =============== +12:59:05.226799 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:05.226858 version@stat F·[] S·0B[] Sc·[] +12:59:05.226870 db@open opening +12:59:05.226897 journal@recovery F·1 +12:59:05.226977 journal@recovery recovering @54 +12:59:05.227118 version@stat F·[] S·0B[] Sc·[] +12:59:05.229637 db@janitor F·2 G·0 +12:59:05.229649 db@open done T·2.774174ms +=============== Jun 10, 2024 (UTC) =============== +12:59:32.412547 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:32.412623 version@stat F·[] S·0B[] Sc·[] +12:59:32.412676 db@open opening +12:59:32.412712 journal@recovery F·1 +12:59:32.412975 journal@recovery recovering @56 +12:59:32.413331 version@stat F·[] S·0B[] Sc·[] +12:59:32.416748 db@janitor F·2 G·0 +12:59:32.416762 db@open done T·4.081375ms +=============== Jul 3, 2024 (UTC) =============== +17:52:58.329151 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:52:58.329241 version@stat F·[] S·0B[] Sc·[] +17:52:58.329256 db@open opening +17:52:58.329285 journal@recovery F·1 +17:52:58.329384 journal@recovery recovering @58 +17:52:58.329519 version@stat F·[] S·0B[] Sc·[] +17:52:58.332058 db@janitor F·2 G·0 +17:52:58.332074 db@open done T·2.812453ms +=============== Jul 3, 2024 (UTC) =============== +17:59:01.713169 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:59:01.713237 version@stat F·[] S·0B[] Sc·[] +17:59:01.713249 db@open opening +17:59:01.713276 journal@recovery F·1 +17:59:01.713370 journal@recovery recovering @60 +17:59:01.713504 version@stat F·[] S·0B[] Sc·[] +17:59:01.716861 db@janitor F·2 G·0 +17:59:01.716876 db@open done T·3.62282ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.957492 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.957616 version@stat F·[] S·0B[] Sc·[] +05:10:40.957629 db@open opening +05:10:40.957658 journal@recovery F·1 +05:10:40.957894 journal@recovery recovering @62 +05:10:40.958388 version@stat F·[] S·0B[] Sc·[] +05:10:40.961122 db@janitor F·2 G·0 +05:10:40.961141 db@open done T·3.507417ms +=============== Oct 17, 2024 (UTC) =============== +06:44:58.803399 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.803504 version@stat F·[] S·0B[] Sc·[] +06:44:58.803519 db@open opening +06:44:58.803556 journal@recovery F·1 +06:44:58.803645 journal@recovery recovering @64 +06:44:58.803912 version@stat F·[] S·0B[] Sc·[] +06:44:58.813046 db@janitor F·2 G·0 +06:44:58.813055 db@open done T·9.533813ms +=============== Oct 17, 2024 (UTC) =============== +06:45:46.688140 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.688207 version@stat F·[] S·0B[] Sc·[] +06:45:46.688217 db@open opening +06:45:46.688244 journal@recovery F·1 +06:45:46.688329 journal@recovery recovering @66 +06:45:46.688475 version@stat F·[] S·0B[] Sc·[] +06:45:46.691028 db@janitor F·2 G·0 +06:45:46.691045 db@open done T·2.823914ms +=============== Oct 17, 2024 (UTC) =============== +06:47:26.306936 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.307069 version@stat F·[] S·0B[] Sc·[] +06:47:26.307085 db@open opening +06:47:26.307124 journal@recovery F·1 +06:47:26.307921 journal@recovery recovering @68 +06:47:26.308103 version@stat F·[] S·0B[] Sc·[] +06:47:26.310856 db@janitor F·2 G·0 +06:47:26.310866 db@open done T·3.776282ms diff --git a/.docker/container-state/nucleus-testnet-data/data/evidence.db/MANIFEST-000071 b/.docker/container-state/nucleus-testnet-data/data/evidence.db/MANIFEST-000071 new file mode 100644 index 0000000000..b4aa9a9181 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/evidence.db/MANIFEST-000071 differ diff --git a/.docker/container-state/nucleus-testnet-data/data/priv_validator_state.json b/.docker/container-state/nucleus-testnet-data/data/priv_validator_state.json new file mode 100644 index 0000000000..70797c4328 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/priv_validator_state.json @@ -0,0 +1,7 @@ +{ + "height": "1207", + "round": 0, + "step": 3, + "signature": "icgswVCjm79XUsbei+n25O+rpUOvze+sTf2mK8ldJjFTDKocmcTEENgkYLa9qUoOTtAw8QgluaDJgzmiiBjwDg==", + "signbytes": "74080211B70400000000000022480A20284E4194EEBC89AFFBE87A98751F5A51DA90E72D8D6345FA1D2BBFEB89343EFC12240801122015262FE05A2AC0DA993FDF279A954CB68A15673F1521A48D39E7D65768595D802A0C0897E6C2B806108C99CBA301320F6E75636C6575732D746573746E6574" +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/000070.log b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/000070.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/CURRENT b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/CURRENT new file mode 100644 index 0000000000..be93edb695 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/CURRENT @@ -0,0 +1 @@ +MANIFEST-000071 diff --git a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/CURRENT.bak b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/CURRENT.bak new file mode 100644 index 0000000000..5893b8f83b --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000069 diff --git a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/LOCK b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/LOCK new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/LOG b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/LOG new file mode 100644 index 0000000000..1210bffa25 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/LOG @@ -0,0 +1,321 @@ +=============== Jun 4, 2024 (UTC) =============== +11:02:17.160862 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:02:17.162765 db@open opening +11:02:17.164575 version@stat F·[] S·0B[] Sc·[] +11:02:17.165193 db@janitor F·2 G·0 +11:02:17.165200 db@open done T·2.42891ms +=============== Jun 6, 2024 (UTC) =============== +05:23:17.021523 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:23:17.021605 version@stat F·[] S·0B[] Sc·[] +05:23:17.021614 db@open opening +05:23:17.021640 journal@recovery F·1 +05:23:17.021803 journal@recovery recovering @1 +05:23:17.022954 version@stat F·[] S·0B[] Sc·[] +05:23:17.025420 db@janitor F·2 G·0 +05:23:17.025429 db@open done T·3.81236ms +=============== Jun 10, 2024 (UTC) =============== +10:09:48.982992 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:09:48.983060 version@stat F·[] S·0B[] Sc·[] +10:09:48.983070 db@open opening +10:09:48.983107 journal@recovery F·1 +10:09:48.983198 journal@recovery recovering @2 +10:09:48.983503 version@stat F·[] S·0B[] Sc·[] +10:09:48.986335 db@janitor F·2 G·0 +10:09:48.986347 db@open done T·3.273207ms +=============== Jun 10, 2024 (UTC) =============== +10:19:47.825155 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:19:47.825238 version@stat F·[] S·0B[] Sc·[] +10:19:47.825251 db@open opening +10:19:47.825278 journal@recovery F·1 +10:19:47.825359 journal@recovery recovering @4 +10:19:47.825768 version@stat F·[] S·0B[] Sc·[] +10:19:47.828609 db@janitor F·2 G·0 +10:19:47.828624 db@open done T·3.369679ms +=============== Jun 10, 2024 (UTC) =============== +10:20:11.676466 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:20:11.676528 version@stat F·[] S·0B[] Sc·[] +10:20:11.676538 db@open opening +10:20:11.676563 journal@recovery F·1 +10:20:11.676652 journal@recovery recovering @6 +10:20:11.676774 version@stat F·[] S·0B[] Sc·[] +10:20:11.679974 db@janitor F·2 G·0 +10:20:11.679984 db@open done T·3.442729ms +=============== Jun 10, 2024 (UTC) =============== +10:23:28.225439 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:23:28.225500 version@stat F·[] S·0B[] Sc·[] +10:23:28.225510 db@open opening +10:23:28.225534 journal@recovery F·1 +10:23:28.225612 journal@recovery recovering @8 +10:23:28.225858 version@stat F·[] S·0B[] Sc·[] +10:23:28.229607 db@janitor F·2 G·0 +10:23:28.229617 db@open done T·4.103634ms +=============== Jun 10, 2024 (UTC) =============== +10:27:08.399412 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:27:08.399485 version@stat F·[] S·0B[] Sc·[] +10:27:08.399496 db@open opening +10:27:08.399521 journal@recovery F·1 +10:27:08.399601 journal@recovery recovering @10 +10:27:08.399820 version@stat F·[] S·0B[] Sc·[] +10:27:08.404066 db@janitor F·2 G·0 +10:27:08.404080 db@open done T·4.580069ms +=============== Jun 10, 2024 (UTC) =============== +10:30:28.013547 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:30:28.013654 version@stat F·[] S·0B[] Sc·[] +10:30:28.013666 db@open opening +10:30:28.013698 journal@recovery F·1 +10:30:28.013791 journal@recovery recovering @12 +10:30:28.013945 version@stat F·[] S·0B[] Sc·[] +10:30:28.016558 db@janitor F·2 G·0 +10:30:28.016571 db@open done T·2.900484ms +=============== Jun 10, 2024 (UTC) =============== +11:11:44.589857 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:11:44.589970 version@stat F·[] S·0B[] Sc·[] +11:11:44.589985 db@open opening +11:11:44.590018 journal@recovery F·1 +11:11:44.590110 journal@recovery recovering @14 +11:11:44.590348 version@stat F·[] S·0B[] Sc·[] +11:11:44.593982 db@janitor F·2 G·0 +11:11:44.593996 db@open done T·4.005713ms +=============== Jun 10, 2024 (UTC) =============== +11:15:00.380028 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:15:00.380099 version@stat F·[] S·0B[] Sc·[] +11:15:00.380109 db@open opening +11:15:00.380136 journal@recovery F·1 +11:15:00.380229 journal@recovery recovering @16 +11:15:00.380384 version@stat F·[] S·0B[] Sc·[] +11:15:00.383015 db@janitor F·2 G·0 +11:15:00.383037 db@open done T·2.921814ms +=============== Jun 10, 2024 (UTC) =============== +11:21:08.202896 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:21:08.203014 version@stat F·[] S·0B[] Sc·[] +11:21:08.203027 db@open opening +11:21:08.203063 journal@recovery F·1 +11:21:08.203151 journal@recovery recovering @18 +11:21:08.203297 version@stat F·[] S·0B[] Sc·[] +11:21:08.205886 db@janitor F·2 G·0 +11:21:08.205897 db@open done T·2.865774ms +=============== Jun 10, 2024 (UTC) =============== +11:22:28.267111 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:22:28.267194 version@stat F·[] S·0B[] Sc·[] +11:22:28.267211 db@open opening +11:22:28.267251 journal@recovery F·1 +11:22:28.269348 journal@recovery recovering @20 +11:22:28.271266 version@stat F·[] S·0B[] Sc·[] +11:22:28.273866 db@janitor F·2 G·0 +11:22:28.273881 db@open done T·6.665886ms +=============== Jun 10, 2024 (UTC) =============== +11:23:15.234286 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:23:15.234355 version@stat F·[] S·0B[] Sc·[] +11:23:15.234365 db@open opening +11:23:15.234391 journal@recovery F·1 +11:23:15.236240 journal@recovery recovering @22 +11:23:15.236377 version@stat F·[] S·0B[] Sc·[] +11:23:15.238935 db@janitor F·2 G·0 +11:23:15.238946 db@open done T·4.577368ms +=============== Jun 10, 2024 (UTC) =============== +11:29:08.603809 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:29:08.603877 version@stat F·[] S·0B[] Sc·[] +11:29:08.603891 db@open opening +11:29:08.603922 journal@recovery F·1 +11:29:08.604005 journal@recovery recovering @24 +11:29:08.604260 version@stat F·[] S·0B[] Sc·[] +11:29:08.607136 db@janitor F·2 G·0 +11:29:08.607152 db@open done T·3.256208ms +=============== Jun 10, 2024 (UTC) =============== +11:30:42.282203 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:42.282303 version@stat F·[] S·0B[] Sc·[] +11:30:42.282316 db@open opening +11:30:42.282344 journal@recovery F·1 +11:30:42.284184 journal@recovery recovering @26 +11:30:42.284358 version@stat F·[] S·0B[] Sc·[] +11:30:42.287694 db@janitor F·2 G·0 +11:30:42.287706 db@open done T·5.385445ms +=============== Jun 10, 2024 (UTC) =============== +11:30:55.669352 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:55.669425 version@stat F·[] S·0B[] Sc·[] +11:30:55.669437 db@open opening +11:30:55.669465 journal@recovery F·1 +11:30:55.671338 journal@recovery recovering @28 +11:30:55.671494 version@stat F·[] S·0B[] Sc·[] +11:30:55.675253 db@janitor F·2 G·0 +11:30:55.675272 db@open done T·5.825019ms +=============== Jun 10, 2024 (UTC) =============== +11:36:36.058381 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:36:36.058454 version@stat F·[] S·0B[] Sc·[] +11:36:36.058468 db@open opening +11:36:36.058498 journal@recovery F·1 +11:36:36.058575 journal@recovery recovering @30 +11:36:36.058874 version@stat F·[] S·0B[] Sc·[] +11:36:36.061701 db@janitor F·2 G·0 +11:36:36.061715 db@open done T·3.241157ms +=============== Jun 10, 2024 (UTC) =============== +12:01:04.941417 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:01:04.941487 version@stat F·[] S·0B[] Sc·[] +12:01:04.941499 db@open opening +12:01:04.941526 journal@recovery F·1 +12:01:04.943164 journal@recovery recovering @32 +12:01:04.943305 version@stat F·[] S·0B[] Sc·[] +12:01:04.946000 db@janitor F·2 G·0 +12:01:04.946011 db@open done T·4.50795ms +=============== Jun 10, 2024 (UTC) =============== +12:02:59.933301 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:02:59.933378 version@stat F·[] S·0B[] Sc·[] +12:02:59.933391 db@open opening +12:02:59.933419 journal@recovery F·1 +12:02:59.933504 journal@recovery recovering @34 +12:02:59.933639 version@stat F·[] S·0B[] Sc·[] +12:02:59.936268 db@janitor F·2 G·0 +12:02:59.936283 db@open done T·2.887636ms +=============== Jun 10, 2024 (UTC) =============== +12:03:19.593709 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:19.593780 version@stat F·[] S·0B[] Sc·[] +12:03:19.593792 db@open opening +12:03:19.593819 journal@recovery F·1 +12:03:19.593901 journal@recovery recovering @36 +12:03:19.594034 version@stat F·[] S·0B[] Sc·[] +12:03:19.596646 db@janitor F·2 G·0 +12:03:19.596665 db@open done T·2.867925ms +=============== Jun 10, 2024 (UTC) =============== +12:04:23.521701 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:04:23.521775 version@stat F·[] S·0B[] Sc·[] +12:04:23.521787 db@open opening +12:04:23.521818 journal@recovery F·1 +12:04:23.521905 journal@recovery recovering @38 +12:04:23.522047 version@stat F·[] S·0B[] Sc·[] +12:04:23.525587 db@janitor F·2 G·0 +12:04:23.525599 db@open done T·3.808243ms +=============== Jun 10, 2024 (UTC) =============== +12:07:46.646403 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:07:46.646479 version@stat F·[] S·0B[] Sc·[] +12:07:46.646491 db@open opening +12:07:46.646522 journal@recovery F·1 +12:07:46.648326 journal@recovery recovering @40 +12:07:46.648463 version@stat F·[] S·0B[] Sc·[] +12:07:46.651146 db@janitor F·2 G·0 +12:07:46.651157 db@open done T·4.661321ms +=============== Jun 10, 2024 (UTC) =============== +12:08:02.042365 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:08:02.042432 version@stat F·[] S·0B[] Sc·[] +12:08:02.042442 db@open opening +12:08:02.042475 journal@recovery F·1 +12:08:02.042558 journal@recovery recovering @42 +12:08:02.042679 version@stat F·[] S·0B[] Sc·[] +12:08:02.045282 db@janitor F·2 G·0 +12:08:02.045297 db@open done T·2.848245ms +=============== Jun 10, 2024 (UTC) =============== +12:27:21.390931 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:27:21.391048 version@stat F·[] S·0B[] Sc·[] +12:27:21.391068 db@open opening +12:27:21.391111 journal@recovery F·1 +12:27:21.391230 journal@recovery recovering @44 +12:27:21.391421 version@stat F·[] S·0B[] Sc·[] +12:27:21.394216 db@janitor F·2 G·0 +12:27:21.394232 db@open done T·3.158397ms +=============== Jun 10, 2024 (UTC) =============== +12:56:43.758036 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:56:43.758118 version@stat F·[] S·0B[] Sc·[] +12:56:43.758132 db@open opening +12:56:43.758159 journal@recovery F·1 +12:56:43.758252 journal@recovery recovering @46 +12:56:43.758481 version@stat F·[] S·0B[] Sc·[] +12:56:43.761326 db@janitor F·2 G·0 +12:56:43.761341 db@open done T·3.203428ms +=============== Jun 10, 2024 (UTC) =============== +12:57:38.328344 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:57:38.328411 version@stat F·[] S·0B[] Sc·[] +12:57:38.328421 db@open opening +12:57:38.328447 journal@recovery F·1 +12:57:38.330433 journal@recovery recovering @48 +12:57:38.330573 version@stat F·[] S·0B[] Sc·[] +12:57:38.333314 db@janitor F·2 G·0 +12:57:38.333323 db@open done T·4.898373ms +=============== Jun 10, 2024 (UTC) =============== +12:58:01.424360 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:01.424428 version@stat F·[] S·0B[] Sc·[] +12:58:01.424439 db@open opening +12:58:01.424465 journal@recovery F·1 +12:58:01.424555 journal@recovery recovering @50 +12:58:01.424862 version@stat F·[] S·0B[] Sc·[] +12:58:01.427853 db@janitor F·2 G·0 +12:58:01.427866 db@open done T·3.42119ms +=============== Jun 10, 2024 (UTC) =============== +12:58:48.947445 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:48.947531 version@stat F·[] S·0B[] Sc·[] +12:58:48.947544 db@open opening +12:58:48.947575 journal@recovery F·1 +12:58:48.949608 journal@recovery recovering @52 +12:58:48.949761 version@stat F·[] S·0B[] Sc·[] +12:58:48.952378 db@janitor F·2 G·0 +12:58:48.952391 db@open done T·4.842882ms +=============== Jun 10, 2024 (UTC) =============== +12:59:05.190019 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:05.190081 version@stat F·[] S·0B[] Sc·[] +12:59:05.190092 db@open opening +12:59:05.190125 journal@recovery F·1 +12:59:05.192033 journal@recovery recovering @54 +12:59:05.194101 version@stat F·[] S·0B[] Sc·[] +12:59:05.196707 db@janitor F·2 G·0 +12:59:05.196719 db@open done T·6.621927ms +=============== Jun 10, 2024 (UTC) =============== +12:59:32.373105 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:32.373178 version@stat F·[] S·0B[] Sc·[] +12:59:32.373198 db@open opening +12:59:32.373227 journal@recovery F·1 +12:59:32.373311 journal@recovery recovering @56 +12:59:32.373559 version@stat F·[] S·0B[] Sc·[] +12:59:32.376912 db@janitor F·2 G·0 +12:59:32.376924 db@open done T·3.720422ms +=============== Jul 3, 2024 (UTC) =============== +17:52:58.295705 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:52:58.295775 version@stat F·[] S·0B[] Sc·[] +17:52:58.295785 db@open opening +17:52:58.295817 journal@recovery F·1 +17:52:58.295900 journal@recovery recovering @58 +17:52:58.296023 version@stat F·[] S·0B[] Sc·[] +17:52:58.298922 db@janitor F·2 G·0 +17:52:58.298936 db@open done T·3.146896ms +=============== Jul 3, 2024 (UTC) =============== +17:59:01.670860 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:59:01.670987 version@stat F·[] S·0B[] Sc·[] +17:59:01.670999 db@open opening +17:59:01.671030 journal@recovery F·1 +17:59:01.671128 journal@recovery recovering @60 +17:59:01.671281 version@stat F·[] S·0B[] Sc·[] +17:59:01.674069 db@janitor F·2 G·0 +17:59:01.674079 db@open done T·3.076606ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.917433 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.917549 version@stat F·[] S·0B[] Sc·[] +05:10:40.917564 db@open opening +05:10:40.917602 journal@recovery F·1 +05:10:40.917711 journal@recovery recovering @62 +05:10:40.918030 version@stat F·[] S·0B[] Sc·[] +05:10:40.922003 db@janitor F·2 G·0 +05:10:40.922017 db@open done T·4.448515ms +=============== Oct 17, 2024 (UTC) =============== +06:44:58.694325 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.694411 version@stat F·[] S·0B[] Sc·[] +06:44:58.694425 db@open opening +06:44:58.694464 journal@recovery F·1 +06:44:58.694541 journal@recovery recovering @64 +06:44:58.694784 version@stat F·[] S·0B[] Sc·[] +06:44:58.713362 db@janitor F·2 G·0 +06:44:58.713379 db@open done T·18.950723ms +=============== Oct 17, 2024 (UTC) =============== +06:45:46.641800 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.641874 version@stat F·[] S·0B[] Sc·[] +06:45:46.641887 db@open opening +06:45:46.641917 journal@recovery F·1 +06:45:46.642008 journal@recovery recovering @66 +06:45:46.642134 version@stat F·[] S·0B[] Sc·[] +06:45:46.646348 db@janitor F·2 G·0 +06:45:46.646361 db@open done T·4.469118ms +=============== Oct 17, 2024 (UTC) =============== +06:47:26.263677 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.263742 version@stat F·[] S·0B[] Sc·[] +06:47:26.263752 db@open opening +06:47:26.263776 journal@recovery F·1 +06:47:26.263857 journal@recovery recovering @68 +06:47:26.264177 version@stat F·[] S·0B[] Sc·[] +06:47:26.269116 db@janitor F·2 G·0 +06:47:26.269129 db@open done T·5.373646ms diff --git a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/MANIFEST-000071 b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/MANIFEST-000071 new file mode 100644 index 0000000000..b4aa9a9181 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/MANIFEST-000071 differ diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/000106.ldb b/.docker/container-state/nucleus-testnet-data/data/state.db/000106.ldb new file mode 100644 index 0000000000..41709b5d5a Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/state.db/000106.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/000107.log b/.docker/container-state/nucleus-testnet-data/data/state.db/000107.log new file mode 100644 index 0000000000..57c76a9bb0 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/state.db/000107.log differ diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/000109.ldb b/.docker/container-state/nucleus-testnet-data/data/state.db/000109.ldb new file mode 100644 index 0000000000..c3d631b9d3 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/state.db/000109.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/CURRENT b/.docker/container-state/nucleus-testnet-data/data/state.db/CURRENT new file mode 100644 index 0000000000..db6fa61f96 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/state.db/CURRENT @@ -0,0 +1 @@ +MANIFEST-000108 diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/CURRENT.bak b/.docker/container-state/nucleus-testnet-data/data/state.db/CURRENT.bak new file mode 100644 index 0000000000..c8e9be65ea --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/state.db/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000104 diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/LOCK b/.docker/container-state/nucleus-testnet-data/data/state.db/LOCK new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/LOG b/.docker/container-state/nucleus-testnet-data/data/state.db/LOG new file mode 100644 index 0000000000..ceaaced552 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/state.db/LOG @@ -0,0 +1,468 @@ +=============== Jun 4, 2024 (UTC) =============== +11:02:17.169926 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:02:17.171758 db@open opening +11:02:17.171929 version@stat F·[] S·0B[] Sc·[] +11:02:17.172614 db@janitor F·2 G·0 +11:02:17.172624 db@open done T·859.55µs +11:20:27.051196 db@close closing +11:20:27.051229 db@close done T·32.611µs +=============== Jun 6, 2024 (UTC) =============== +05:23:17.034700 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:23:17.034755 version@stat F·[] S·0B[] Sc·[] +05:23:17.034764 db@open opening +05:23:17.034786 journal@recovery F·1 +05:23:17.034963 journal@recovery recovering @1 +05:23:17.038139 memdb@flush created L0@2 N·1090 S·217KiB "abc..y:1,v6":"val..:99,v488" +05:23:17.040121 version@stat F·[1] S·217KiB[217KiB] Sc·[0.25] +05:23:17.042809 db@janitor F·3 G·0 +05:23:17.042819 db@open done T·8.051963ms +05:43:54.555468 db@close closing +05:43:54.555490 db@close done T·22.451µs +=============== Jun 10, 2024 (UTC) =============== +10:09:49.001598 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:09:49.001771 version@stat F·[1] S·217KiB[217KiB] Sc·[0.25] +10:09:49.001787 db@open opening +10:09:49.001834 journal@recovery F·1 +10:09:49.001967 journal@recovery recovering @3 +10:09:49.006540 memdb@flush created L0@5 N·1235 S·238KiB "abc..218,v1092":"val..466,v2324" +10:09:49.006846 version@stat F·[2] S·456KiB[456KiB] Sc·[0.50] +10:09:49.010108 db@janitor F·4 G·0 +10:09:49.010120 db@open done T·8.329177ms +10:17:29.618645 table@compaction L0·2 -> L1·0 S·456KiB Q·2782 +10:17:29.622427 table@build created L1@8 N·1398 S·192KiB "abc..y:1,v6":"val..:99,v488" +10:17:29.622451 version@stat F·[0 1] S·192KiB[0B 192KiB] Sc·[0.00 0.00] +10:17:29.623984 table@compaction committed F-1 S-263KiB Ke·0 D·927 T·5.320955ms +10:17:29.624090 table@remove removed @2 +10:19:38.331989 db@close closing +10:19:38.332018 db@close done T·29.511µs +=============== Jun 10, 2024 (UTC) =============== +10:19:47.845603 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:19:47.845689 version@stat F·[0 1] S·192KiB[0B 192KiB] Sc·[0.00 0.00] +10:19:47.845699 db@open opening +10:19:47.845727 journal@recovery F·1 +10:19:47.845811 journal@recovery recovering @6 +10:19:47.848260 memdb@flush created L0@9 N·585 S·107KiB "abc..465,v2328":"val..583,v2910" +10:19:47.848595 version@stat F·[1 1] S·300KiB[107KiB 192KiB] Sc·[0.25 0.00] +10:19:47.851412 db@janitor F·5 G·1 +10:19:47.851418 db@janitor removing table-5 +10:19:47.851496 db@open done T·5.792829ms +=============== Jun 10, 2024 (UTC) =============== +10:20:11.696554 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:20:11.696634 version@stat F·[1 1] S·300KiB[107KiB 192KiB] Sc·[0.25 0.00] +10:20:11.696646 db@open opening +10:20:11.696673 journal@recovery F·1 +10:20:11.696758 journal@recovery recovering @10 +10:20:11.696889 version@stat F·[1 1] S·300KiB[107KiB 192KiB] Sc·[0.25 0.00] +10:20:11.699600 db@janitor F·4 G·0 +10:20:11.699614 db@open done T·2.964835ms +10:23:04.295043 db@close closing +10:23:04.295073 db@close done T·29.89µs +=============== Jun 10, 2024 (UTC) =============== +10:23:28.245438 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:23:28.245925 version@stat F·[1 1] S·300KiB[107KiB 192KiB] Sc·[0.25 0.00] +10:23:28.245945 db@open opening +10:23:28.246022 journal@recovery F·1 +10:23:28.246220 journal@recovery recovering @12 +10:23:28.247738 memdb@flush created L0@14 N·170 S·33KiB "abc..582,v2914":"val..617,v3081" +10:23:28.248222 version@stat F·[2 1] S·333KiB[140KiB 192KiB] Sc·[0.50 0.00] +10:23:28.251598 db@janitor F·5 G·0 +10:23:28.251610 db@open done T·5.660847ms +10:26:58.662998 db@close closing +10:26:58.663040 db@close done T·41.29µs +=============== Jun 10, 2024 (UTC) =============== +10:27:08.420118 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:27:08.420212 version@stat F·[2 1] S·333KiB[140KiB 192KiB] Sc·[0.50 0.00] +10:27:08.420223 db@open opening +10:27:08.420254 journal@recovery F·1 +10:27:08.420427 journal@recovery recovering @15 +10:27:08.421948 memdb@flush created L0@17 N·210 S·39KiB "abc..616,v3085":"val..659,v3292" +10:27:08.422255 version@stat F·[3 1] S·372KiB[179KiB 192KiB] Sc·[0.75 0.00] +10:27:08.425139 db@janitor F·6 G·0 +10:27:08.425151 db@open done T·4.924371ms +10:28:46.710173 db@close closing +10:28:46.710215 db@close done T·41.64µs +=============== Jun 10, 2024 (UTC) =============== +10:30:28.035683 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:30:28.035773 version@stat F·[3 1] S·372KiB[179KiB 192KiB] Sc·[0.75 0.00] +10:30:28.035785 db@open opening +10:30:28.035824 journal@recovery F·1 +10:30:28.035921 journal@recovery recovering @18 +10:30:28.036916 memdb@flush created L0@20 N·95 S·19KiB "abc..658,v3296":"val..678,v3388" +10:30:28.037233 version@stat F·[4 1] S·392KiB[199KiB 192KiB] Sc·[1.00 0.00] +10:30:28.039918 db@janitor F·7 G·0 +10:30:28.039932 db@open done T·4.142944ms +10:30:28.039957 table@compaction L0·4 -> L1·1 S·392KiB Q·3391 +10:30:28.044217 table@build created L1@23 N·2034 S·273KiB "abc..y:1,v6":"val..:99,v488" +10:30:28.044258 version@stat F·[0 1] S·273KiB[0B 273KiB] Sc·[0.00 0.00] +10:30:28.045300 table@compaction committed F-4 S-118KiB Ke·0 D·424 T·5.324424ms +10:30:28.045359 table@remove removed @17 +10:30:28.045459 table@remove removed @14 +10:30:28.045509 table@remove removed @9 +10:30:28.045570 table@remove removed @8 +10:34:40.140467 db@close closing +10:34:40.140495 db@close done T·28.811µs +=============== Jun 10, 2024 (UTC) =============== +11:11:44.613640 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:11:44.613730 version@stat F·[0 1] S·273KiB[0B 273KiB] Sc·[0.00 0.00] +11:11:44.613741 db@open opening +11:11:44.613768 journal@recovery F·1 +11:11:44.613936 journal@recovery recovering @21 +11:11:44.615591 memdb@flush created L0@24 N·250 S·46KiB "abc..677,v3392":"val..728,v3639" +11:11:44.615920 version@stat F·[1 1] S·320KiB[46KiB 273KiB] Sc·[0.25 0.00] +11:11:44.618540 db@janitor F·5 G·1 +11:11:44.618547 db@janitor removing table-20 +11:11:44.618585 db@open done T·4.83984ms +11:12:55.522384 table@compaction L0·1 -> L1·1 S·320KiB Q·3712 +11:12:55.525587 table@build created L1@27 N·2184 S·292KiB "abc..y:1,v6":"val..:99,v488" +11:12:55.525608 version@stat F·[0 1] S·292KiB[0B 292KiB] Sc·[0.00 0.00] +11:12:55.526197 table@compaction committed F-1 S-28KiB Ke·0 D·100 T·3.792921ms +11:12:55.526304 table@remove removed @23 +11:13:46.210625 db@close closing +11:13:46.210682 db@close done T·56.52µs +=============== Jun 10, 2024 (UTC) =============== +11:15:00.401087 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:15:00.401167 version@stat F·[0 1] S·292KiB[0B 292KiB] Sc·[0.00 0.00] +11:15:00.401179 db@open opening +11:15:00.401215 journal@recovery F·1 +11:15:00.401300 journal@recovery recovering @25 +11:15:00.402497 memdb@flush created L0@28 N·120 S·44KiB "abc..727,v3643":"val..752,v3760" +11:15:00.402737 version@stat F·[1 1] S·336KiB[44KiB 292KiB] Sc·[0.25 0.00] +11:15:00.405667 db@janitor F·5 G·1 +11:15:00.405675 db@janitor removing table-24 +11:15:00.405715 db@open done T·4.533188ms +=============== Jun 10, 2024 (UTC) =============== +11:21:08.225008 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:21:08.225077 version@stat F·[1 1] S·336KiB[44KiB 292KiB] Sc·[0.25 0.00] +11:21:08.225087 db@open opening +11:21:08.225120 journal@recovery F·1 +11:21:08.225204 journal@recovery recovering @29 +11:21:08.225515 version@stat F·[1 1] S·336KiB[44KiB 292KiB] Sc·[0.25 0.00] +11:21:08.228249 db@janitor F·4 G·0 +11:21:08.228259 db@open done T·3.163386ms +=============== Jun 10, 2024 (UTC) =============== +11:22:28.290853 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:22:28.290916 version@stat F·[1 1] S·336KiB[44KiB 292KiB] Sc·[0.25 0.00] +11:22:28.290927 db@open opening +11:22:28.290952 journal@recovery F·1 +11:22:28.291031 journal@recovery recovering @31 +11:22:28.291738 memdb@flush created L0@33 N·5 S·1KiB "abc..751,v3764":"val..753,v3766" +11:22:28.291843 version@stat F·[2 1] S·338KiB[45KiB 292KiB] Sc·[0.50 0.00] +11:22:28.294374 db@janitor F·5 G·0 +11:22:28.294388 db@open done T·3.457658ms +11:22:57.093696 db@close closing +11:22:57.093729 db@close done T·32.36µs +=============== Jun 10, 2024 (UTC) =============== +11:23:15.255337 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:23:15.255429 version@stat F·[2 1] S·338KiB[45KiB 292KiB] Sc·[0.50 0.00] +11:23:15.255440 db@open opening +11:23:15.255468 journal@recovery F·1 +11:23:15.255632 journal@recovery recovering @34 +11:23:15.256593 memdb@flush created L0@36 N·25 S·5KiB "abc..752,v3770":"val..758,v3792" +11:23:15.256919 version@stat F·[3 1] S·343KiB[51KiB 292KiB] Sc·[0.75 0.00] +11:23:15.259471 db@janitor F·6 G·0 +11:23:15.259483 db@open done T·4.039333ms +11:27:45.632206 table@compaction L0·3 -> L1·1 S·343KiB Q·4060 +11:27:45.635970 table@build created L1@39 N·2274 S·314KiB "abc..y:1,v6":"val..:99,v488" +11:27:45.635994 version@stat F·[0 1] S·314KiB[0B 314KiB] Sc·[0.00 0.00] +11:27:45.637490 table@compaction committed F-3 S-29KiB Ke·0 D·60 T·5.261094ms +11:27:45.637552 table@remove removed @33 +11:27:45.637596 table@remove removed @28 +11:27:45.637689 table@remove removed @27 +11:28:46.931096 db@close closing +11:28:46.931135 db@close done T·38.64µs +=============== Jun 10, 2024 (UTC) =============== +11:29:08.628446 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:29:08.628530 version@stat F·[0 1] S·314KiB[0B 314KiB] Sc·[0.00 0.00] +11:29:08.628543 db@open opening +11:29:08.628575 journal@recovery F·1 +11:29:08.628662 journal@recovery recovering @37 +11:29:08.630178 memdb@flush created L0@40 N·330 S·60KiB "abc..757,v3796":"val..824,v4123" +11:29:08.630297 version@stat F·[1 1] S·375KiB[60KiB 314KiB] Sc·[0.25 0.00] +11:29:08.633799 db@janitor F·5 G·1 +11:29:08.633809 db@janitor removing table-36 +11:29:08.633844 db@open done T·5.296244ms +11:30:37.043777 db@close closing +11:30:37.043826 db@close done T·46.751µs +=============== Jun 10, 2024 (UTC) =============== +11:30:42.312510 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:42.312612 version@stat F·[1 1] S·375KiB[60KiB 314KiB] Sc·[0.25 0.00] +11:30:42.312630 db@open opening +11:30:42.312676 journal@recovery F·1 +11:30:42.312783 journal@recovery recovering @41 +11:30:42.313896 memdb@flush created L0@43 N·85 S·16KiB "abc..823,v4127":"val..841,v4209" +11:30:42.314203 version@stat F·[2 1] S·391KiB[77KiB 314KiB] Sc·[0.50 0.00] +11:30:42.316961 db@janitor F·5 G·0 +11:30:42.316978 db@open done T·4.342496ms +=============== Jun 10, 2024 (UTC) =============== +11:30:55.695865 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:55.695992 version@stat F·[2 1] S·391KiB[77KiB 314KiB] Sc·[0.50 0.00] +11:30:55.696007 db@open opening +11:30:55.696041 journal@recovery F·1 +11:30:55.696135 journal@recovery recovering @44 +11:30:55.698557 memdb@flush created L0@46 N·5 S·1KiB "abc..840,v4213":"val..842,v4215" +11:30:55.698730 version@stat F·[3 1] S·392KiB[78KiB 314KiB] Sc·[0.75 0.00] +11:30:55.701328 db@janitor F·6 G·0 +11:30:55.701348 db@open done T·5.337075ms +11:35:56.722638 db@close closing +11:35:56.722688 db@close done T·49.24µs +=============== Jun 10, 2024 (UTC) =============== +11:36:36.082968 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:36:36.083044 version@stat F·[3 1] S·392KiB[78KiB 314KiB] Sc·[0.75 0.00] +11:36:36.083056 db@open opening +11:36:36.083087 journal@recovery F·1 +11:36:36.083363 journal@recovery recovering @47 +11:36:36.085402 memdb@flush created L0@49 N·300 S·54KiB "abc..841,v4219":"val..902,v4516" +11:36:36.085536 version@stat F·[4 1] S·447KiB[133KiB 314KiB] Sc·[1.00 0.00] +11:36:36.088497 db@janitor F·7 G·0 +11:36:36.088511 db@open done T·5.450696ms +11:36:36.088549 table@compaction L0·4 -> L1·1 S·447KiB Q·4519 +11:36:36.093288 table@build created L1@52 N·2706 S·369KiB "abc..y:1,v6":"val..:99,v488" +11:36:36.093313 version@stat F·[0 1] S·369KiB[0B 369KiB] Sc·[0.00 0.00] +11:36:36.094811 table@compaction committed F-4 S-78KiB Ke·0 D·288 T·6.229052ms +11:36:36.094881 table@remove removed @46 +11:36:36.094975 table@remove removed @43 +11:36:36.095038 table@remove removed @40 +11:36:36.095147 table@remove removed @39 +11:40:48.235780 db@close closing +11:40:48.235814 db@close done T·33.03µs +=============== Jun 10, 2024 (UTC) =============== +12:01:04.967260 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:01:04.967353 version@stat F·[0 1] S·369KiB[0B 369KiB] Sc·[0.00 0.00] +12:01:04.967366 db@open opening +12:01:04.967398 journal@recovery F·1 +12:01:04.969218 journal@recovery recovering @50 +12:01:04.970473 memdb@flush created L0@53 N·250 S·46KiB "abc..901,v4520":"val..952,v4767" +12:01:04.970598 version@stat F·[1 1] S·416KiB[46KiB 369KiB] Sc·[0.25 0.00] +12:01:04.974974 db@janitor F·5 G·1 +12:01:04.974982 db@janitor removing table-49 +12:01:04.975024 db@open done T·7.653087ms +12:01:23.716876 db@close closing +12:01:23.716904 db@close done T·27.41µs +=============== Jun 10, 2024 (UTC) =============== +12:02:59.958593 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:02:59.958685 version@stat F·[1 1] S·416KiB[46KiB 369KiB] Sc·[0.25 0.00] +12:02:59.958697 db@open opening +12:02:59.958725 journal@recovery F·1 +12:02:59.958811 journal@recovery recovering @54 +12:02:59.959590 memdb@flush created L0@56 N·15 S·2KiB "abc..951,v4771":"val..955,v4783" +12:02:59.959926 version@stat F·[2 1] S·419KiB[49KiB 369KiB] Sc·[0.50 0.00] +12:02:59.962721 db@janitor F·5 G·0 +12:02:59.962740 db@open done T·4.037675ms +=============== Jun 10, 2024 (UTC) =============== +12:03:19.619068 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:19.619159 version@stat F·[2 1] S·419KiB[49KiB 369KiB] Sc·[0.50 0.00] +12:03:19.619171 db@open opening +12:03:19.619209 journal@recovery F·1 +12:03:19.619293 journal@recovery recovering @57 +12:03:19.620092 memdb@flush created L0@59 N·10 S·2KiB "abc..954,v4787":"val..957,v4794" +12:03:19.620371 version@stat F·[3 1] S·421KiB[52KiB 369KiB] Sc·[0.75 0.00] +12:03:19.623516 db@janitor F·6 G·0 +12:03:19.623536 db@open done T·4.359919ms +=============== Jun 10, 2024 (UTC) =============== +12:04:23.547604 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:04:23.547706 version@stat F·[3 1] S·421KiB[52KiB 369KiB] Sc·[0.75 0.00] +12:04:23.547724 db@open opening +12:04:23.547777 journal@recovery F·1 +12:04:23.550018 journal@recovery recovering @60 +12:04:23.550785 memdb@flush created L0@62 N·5 S·1KiB "abc..956,v4798":"val..958,v4800" +12:04:23.550942 version@stat F·[4 1] S·423KiB[53KiB 369KiB] Sc·[1.00 0.00] +12:04:23.554488 db@janitor F·7 G·0 +12:04:23.554502 db@open done T·6.771829ms +12:04:23.554540 table@compaction L0·4 -> L1·1 S·423KiB Q·4803 +12:04:23.560321 table@build created L1@65 N·2874 S·390KiB "abc..y:1,v6":"val..:99,v488" +12:04:23.560358 version@stat F·[0 1] S·390KiB[0B 390KiB] Sc·[0.00 0.00] +12:04:23.561465 table@compaction committed F-4 S-32KiB Ke·0 D·112 T·6.830041ms +12:04:23.561559 table@remove removed @59 +12:04:23.561683 table@remove removed @56 +12:04:23.561737 table@remove removed @53 +12:04:23.561844 table@remove removed @52 +=============== Jun 10, 2024 (UTC) =============== +12:07:46.670266 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:07:46.670379 version@stat F·[0 1] S·390KiB[0B 390KiB] Sc·[0.00 0.00] +12:07:46.670400 db@open opening +12:07:46.670446 journal@recovery F·1 +12:07:46.670707 journal@recovery recovering @63 +12:07:46.671008 version@stat F·[0 1] S·390KiB[0B 390KiB] Sc·[0.00 0.00] +12:07:46.673726 db@janitor F·4 G·1 +12:07:46.673744 db@janitor removing table-62 +12:07:46.673784 db@open done T·3.3777ms +=============== Jun 10, 2024 (UTC) =============== +12:08:02.066883 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:08:02.066971 version@stat F·[0 1] S·390KiB[0B 390KiB] Sc·[0.00 0.00] +12:08:02.066983 db@open opening +12:08:02.067013 journal@recovery F·1 +12:08:02.067205 journal@recovery recovering @66 +12:08:02.067764 version@stat F·[0 1] S·390KiB[0B 390KiB] Sc·[0.00 0.00] +12:08:02.070467 db@janitor F·3 G·0 +12:08:02.070483 db@open done T·3.495051ms +=============== Jun 10, 2024 (UTC) =============== +12:27:21.423944 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:27:21.424164 version@stat F·[0 1] S·390KiB[0B 390KiB] Sc·[0.00 0.00] +12:27:21.424182 db@open opening +12:27:21.424225 journal@recovery F·1 +12:27:21.424560 journal@recovery recovering @68 +12:27:21.425123 version@stat F·[0 1] S·390KiB[0B 390KiB] Sc·[0.00 0.00] +12:27:21.427788 db@janitor F·3 G·0 +12:27:21.427802 db@open done T·3.608771ms +=============== Jun 10, 2024 (UTC) =============== +12:56:43.782384 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:56:43.782466 version@stat F·[0 1] S·390KiB[0B 390KiB] Sc·[0.00 0.00] +12:56:43.782481 db@open opening +12:56:43.782514 journal@recovery F·1 +12:56:43.784553 journal@recovery recovering @70 +12:56:43.785342 memdb@flush created L0@72 N·10 S·2KiB "abc..957,v4804":"val..960,v4811" +12:56:43.785469 version@stat F·[1 1] S·393KiB[2KiB 390KiB] Sc·[0.25 0.00] +12:56:43.788122 db@janitor F·4 G·0 +12:56:43.788135 db@open done T·5.649129ms +=============== Jun 10, 2024 (UTC) =============== +12:57:38.353522 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:57:38.353605 version@stat F·[1 1] S·393KiB[2KiB 390KiB] Sc·[0.25 0.00] +12:57:38.353620 db@open opening +12:57:38.353659 journal@recovery F·1 +12:57:38.353761 journal@recovery recovering @73 +12:57:38.353914 version@stat F·[1 1] S·393KiB[2KiB 390KiB] Sc·[0.25 0.00] +12:57:38.356549 db@janitor F·4 G·0 +12:57:38.356566 db@open done T·2.941386ms +=============== Jun 10, 2024 (UTC) =============== +12:58:01.450715 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:01.450788 version@stat F·[1 1] S·393KiB[2KiB 390KiB] Sc·[0.25 0.00] +12:58:01.450800 db@open opening +12:58:01.450834 journal@recovery F·1 +12:58:01.451013 journal@recovery recovering @75 +12:58:01.451751 memdb@flush created L0@77 N·5 S·1KiB "abc..959,v4815":"val..961,v4817" +12:58:01.451877 version@stat F·[2 1] S·394KiB[3KiB 390KiB] Sc·[0.50 0.00] +12:58:01.454428 db@janitor F·5 G·0 +12:58:01.454441 db@open done T·3.635832ms +=============== Jun 10, 2024 (UTC) =============== +12:58:48.972542 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:48.972608 version@stat F·[2 1] S·394KiB[3KiB 390KiB] Sc·[0.50 0.00] +12:58:48.972619 db@open opening +12:58:48.972645 journal@recovery F·1 +12:58:48.972799 journal@recovery recovering @78 +12:58:48.973750 memdb@flush created L0@80 N·10 S·2KiB "abc..960,v4821":"val..963,v4828" +12:58:48.975745 version@stat F·[3 1] S·396KiB[6KiB 390KiB] Sc·[0.75 0.00] +12:58:48.978310 db@janitor F·6 G·0 +12:58:48.978328 db@open done T·5.706739ms +=============== Jun 10, 2024 (UTC) =============== +12:59:05.216181 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:05.216268 version@stat F·[3 1] S·396KiB[6KiB 390KiB] Sc·[0.75 0.00] +12:59:05.216281 db@open opening +12:59:05.216313 journal@recovery F·1 +12:59:05.216396 journal@recovery recovering @81 +12:59:05.216630 version@stat F·[3 1] S·396KiB[6KiB 390KiB] Sc·[0.75 0.00] +12:59:05.219289 db@janitor F·6 G·0 +12:59:05.219303 db@open done T·3.017166ms +=============== Jun 10, 2024 (UTC) =============== +12:59:32.398825 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:32.398920 version@stat F·[3 1] S·396KiB[6KiB 390KiB] Sc·[0.75 0.00] +12:59:32.398933 db@open opening +12:59:32.398966 journal@recovery F·1 +12:59:32.401293 journal@recovery recovering @83 +12:59:32.402150 memdb@flush created L0@85 N·5 S·1KiB "abc..962,v4832":"val..964,v4834" +12:59:32.402298 version@stat F·[4 1] S·398KiB[7KiB 390KiB] Sc·[1.00 0.00] +12:59:32.404889 db@janitor F·7 G·0 +12:59:32.404903 db@open done T·5.965162ms +12:59:32.404938 table@compaction L0·4 -> L1·1 S·398KiB Q·4837 +12:59:32.410153 table@build created L1@88 N·2892 S·392KiB "abc..y:1,v6":"val..:99,v488" +12:59:32.410177 version@stat F·[0 1] S·392KiB[0B 392KiB] Sc·[0.00 0.00] +12:59:32.411742 table@compaction committed F-4 S-5KiB Ke·0 D·12 T·6.775389ms +12:59:32.411819 table@remove removed @80 +12:59:32.411922 table@remove removed @77 +12:59:32.411960 table@remove removed @72 +12:59:32.412071 table@remove removed @65 +=============== Jul 3, 2024 (UTC) =============== +17:52:58.319428 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:52:58.319516 version@stat F·[0 1] S·392KiB[0B 392KiB] Sc·[0.00 0.00] +17:52:58.319528 db@open opening +17:52:58.319556 journal@recovery F·1 +17:52:58.319644 journal@recovery recovering @86 +17:52:58.319923 version@stat F·[0 1] S·392KiB[0B 392KiB] Sc·[0.00 0.00] +17:52:58.323031 db@janitor F·4 G·1 +17:52:58.323039 db@janitor removing table-85 +17:52:58.323074 db@open done T·3.54148ms +17:58:44.101865 db@close closing +17:58:44.101899 db@close done T·33.25µs +=============== Jul 3, 2024 (UTC) =============== +17:59:01.699027 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:59:01.699336 version@stat F·[0 1] S·392KiB[0B 392KiB] Sc·[0.00 0.00] +17:59:01.699350 db@open opening +17:59:01.699408 journal@recovery F·1 +17:59:01.700331 journal@recovery recovering @89 +17:59:01.701961 memdb@flush created L0@91 N·345 S·87KiB "abc..000,v5023":"val..999,v5010" +17:59:01.702080 version@stat F·[1 1] S·480KiB[87KiB 392KiB] Sc·[0.25 0.00] +17:59:01.705270 db@janitor F·4 G·0 +17:59:01.705289 db@open done T·5.934209ms +17:59:27.006738 db@close closing +17:59:27.006773 db@close done T·35.621µs +=============== Oct 17, 2024 (UTC) =============== +05:10:40.948045 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.948116 version@stat F·[1 1] S·480KiB[87KiB 392KiB] Sc·[0.25 0.00] +05:10:40.948126 db@open opening +05:10:40.948156 journal@recovery F·1 +05:10:40.948359 journal@recovery recovering @92 +05:10:40.949167 memdb@flush created L0@94 N·25 S·5KiB "abc..032,v5184":"val..038,v5206" +05:10:40.949303 version@stat F·[2 1] S·485KiB[93KiB 392KiB] Sc·[0.50 0.00] +05:10:40.952715 db@janitor F·5 G·0 +05:10:40.952726 db@open done T·4.596875ms +05:10:52.975901 table@compaction L0·2 -> L1·1 S·485KiB Q·5219 +05:10:52.980319 table@build created L1@97 N·3114 S·432KiB "abc..y:1,v6":"val..999,v5010" +05:10:52.980342 version@stat F·[0 1] S·432KiB[0B 432KiB] Sc·[0.00 0.00] +05:10:52.980914 table@compaction committed F-2 S-53KiB Ke·0 D·148 T·4.995718ms +05:10:52.980988 table@remove removed @91 +05:10:52.981079 table@remove removed @88 +05:22:45.688861 db@close closing +05:22:45.688894 db@close done T·32.78µs +=============== Oct 17, 2024 (UTC) =============== +06:44:58.757687 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.757762 version@stat F·[0 1] S·432KiB[0B 432KiB] Sc·[0.00 0.00] +06:44:58.757772 db@open opening +06:44:58.757803 journal@recovery F·1 +06:44:58.758114 journal@recovery recovering @95 +06:44:58.766158 memdb@flush created L0@98 N·720 S·160KiB "abc..037,v5210":"val..182,v5927" +06:44:58.768181 version@stat F·[1 1] S·593KiB[160KiB 432KiB] Sc·[0.25 0.00] +06:44:58.782781 db@janitor F·5 G·1 +06:44:58.782787 db@janitor removing table-94 +06:44:58.782815 db@open done T·25.040516ms +06:45:31.737199 table@compaction L0·1 -> L1·1 S·593KiB Q·5960 +06:45:31.742113 table@build created L1@101 N·3546 S·502KiB "abc..y:1,v6":"val..999,v5010" +06:45:31.742138 version@stat F·[0 1] S·502KiB[0B 502KiB] Sc·[0.00 0.00] +06:45:31.742713 table@compaction committed F-1 S-90KiB Ke·0 D·288 T·5.489518ms +06:45:31.742852 table@remove removed @97 +06:45:36.822729 db@close closing +06:45:36.822759 db@close done T·30.251µs +=============== Oct 17, 2024 (UTC) =============== +06:45:46.672283 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.672368 version@stat F·[0 1] S·502KiB[0B 502KiB] Sc·[0.00 0.00] +06:45:46.672378 db@open opening +06:45:46.672405 journal@recovery F·1 +06:45:46.672600 journal@recovery recovering @99 +06:45:46.673679 memdb@flush created L0@102 N·35 S·14KiB "abc..181,v5931":"val..189,v5963" +06:45:46.674067 version@stat F·[1 1] S·517KiB[14KiB 502KiB] Sc·[0.25 0.00] +06:45:46.676665 db@janitor F·5 G·1 +06:45:46.676674 db@janitor removing table-98 +06:45:46.676808 db@open done T·4.425948ms +06:46:17.600950 table@compaction L0·1 -> L1·1 S·517KiB Q·5996 +06:46:17.606418 table@build created L1@105 N·3567 S·509KiB "abc..y:1,v6":"val..999,v5010" +06:46:17.606444 version@stat F·[0 1] S·509KiB[0B 509KiB] Sc·[0.00 0.00] +06:46:17.607473 table@compaction committed F-1 S-8KiB Ke·0 D·14 T·6.501365ms +06:46:17.607615 table@remove removed @101 +06:47:06.119645 db@close closing +06:47:06.119690 db@close done T·44.54µs +=============== Oct 17, 2024 (UTC) =============== +06:47:26.296053 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.296123 version@stat F·[0 1] S·509KiB[0B 509KiB] Sc·[0.00 0.00] +06:47:26.296133 db@open opening +06:47:26.296160 journal@recovery F·1 +06:47:26.296240 journal@recovery recovering @103 +06:47:26.297208 memdb@flush created L0@106 N·75 S·14KiB "abc..188,v5967":"val..204,v6039" +06:47:26.297326 version@stat F·[1 1] S·523KiB[14KiB 509KiB] Sc·[0.25 0.00] +06:47:26.300025 db@janitor F·5 G·1 +06:47:26.300032 db@janitor removing table-102 +06:47:26.300068 db@open done T·3.931804ms +06:47:51.340428 table@compaction L0·1 -> L1·1 S·523KiB Q·6062 +06:47:51.345463 table@build created L1@109 N·3612 S·515KiB "abc..y:1,v6":"val..999,v5010" +06:47:51.345490 version@stat F·[0 1] S·515KiB[0B 515KiB] Sc·[0.00 0.01] +06:47:51.347253 table@compaction committed F-1 S-8KiB Ke·0 D·30 T·6.790988ms +06:47:51.347409 table@remove removed @105 +06:47:53.050487 db@close closing +06:47:53.050515 db@close done T·27.49µs diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/MANIFEST-000108 b/.docker/container-state/nucleus-testnet-data/data/state.db/MANIFEST-000108 new file mode 100644 index 0000000000..3e0e6991cd Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/state.db/MANIFEST-000108 differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000095.ldb b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000095.ldb new file mode 100644 index 0000000000..d60494d2f4 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000095.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000096.ldb b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000096.ldb new file mode 100644 index 0000000000..ced510724b Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000096.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000099.ldb b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000099.ldb new file mode 100644 index 0000000000..835a70959c Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000099.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000102.ldb b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000102.ldb new file mode 100644 index 0000000000..b1a9417bb5 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000102.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000103.log b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000103.log new file mode 100644 index 0000000000..83c9380d98 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000103.log differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/CURRENT b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/CURRENT new file mode 100644 index 0000000000..c8e9be65ea --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/CURRENT @@ -0,0 +1 @@ +MANIFEST-000104 diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/CURRENT.bak b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/CURRENT.bak new file mode 100644 index 0000000000..ed5ac889a6 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000101 diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/LOCK b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/LOCK new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/LOG b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/LOG new file mode 100644 index 0000000000..d472949dbb --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/LOG @@ -0,0 +1,401 @@ +=============== Jun 4, 2024 (UTC) =============== +11:02:17.174284 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:02:17.176196 db@open opening +11:02:17.178035 version@stat F·[] S·0B[] Sc·[] +11:02:17.178648 db@janitor F·2 G·0 +11:02:17.178656 db@open done T·2.45342ms +=============== Jun 6, 2024 (UTC) =============== +05:23:17.043151 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:23:17.043211 version@stat F·[] S·0B[] Sc·[] +05:23:17.043220 db@open opening +05:23:17.043243 journal@recovery F·1 +05:23:17.044979 journal@recovery recovering @1 +05:23:17.050073 memdb@flush created L0@2 N·7767 S·122KiB "\x17\xc7\v..\x0f\x89\xe4,v7242":"\xe2\x98\xcc..\xed\xa6\xd1,v3296" +05:23:17.050182 version@stat F·[1] S·122KiB[122KiB] Sc·[0.25] +05:23:17.052762 db@janitor F·3 G·0 +05:23:17.052770 db@open done T·9.548125ms +=============== Jun 10, 2024 (UTC) =============== +10:09:49.011112 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:09:49.011205 version@stat F·[1] S·122KiB[122KiB] Sc·[0.25] +10:09:49.011217 db@open opening +10:09:49.011244 journal@recovery F·1 +10:09:49.011426 journal@recovery recovering @3 +10:09:49.017663 memdb@flush created L0@5 N·8783 S·135KiB "blo..\x01\xc0\xda,v7769":"\xea\xa0\\..ʞ\xcc,v16411" +10:09:49.019152 version@stat F·[2] S·257KiB[257KiB] Sc·[0.50] +10:09:49.022013 db@janitor F·4 G·0 +10:09:49.022025 db@open done T·10.804059ms +=============== Jun 10, 2024 (UTC) =============== +10:19:47.851934 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:19:47.852029 version@stat F·[2] S·257KiB[257KiB] Sc·[0.50] +10:19:47.852041 db@open opening +10:19:47.852071 journal@recovery F·1 +10:19:47.854038 journal@recovery recovering @6 +10:19:47.857092 memdb@flush created L0@8 N·4095 S·59KiB "blo..\x01\xc1\xd1,v16553":"blo..k\x00\x01,v20623" +10:19:47.857269 version@stat F·[3] S·317KiB[317KiB] Sc·[0.75] +10:19:47.860203 db@janitor F·5 G·0 +10:19:47.860216 db@open done T·8.170569ms +=============== Jun 10, 2024 (UTC) =============== +10:20:11.700021 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:20:11.700094 version@stat F·[3] S·317KiB[317KiB] Sc·[0.75] +10:20:11.700104 db@open opening +10:20:11.700134 journal@recovery F·1 +10:20:11.700362 journal@recovery recovering @9 +10:20:11.700777 version@stat F·[3] S·317KiB[317KiB] Sc·[0.75] +10:20:11.703476 db@janitor F·5 G·0 +10:20:11.703490 db@open done T·3.381668ms +=============== Jun 10, 2024 (UTC) =============== +10:23:28.252008 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:23:28.252086 version@stat F·[3] S·317KiB[317KiB] Sc·[0.75] +10:23:28.252097 db@open opening +10:23:28.252131 journal@recovery F·1 +10:23:28.252233 journal@recovery recovering @11 +10:23:28.253604 memdb@flush created L0@13 N·1214 S·18KiB "blo..\x01\xc2F,v20649":"\xf2~\xe4..\x05\xa4\xc4,v21827" +10:23:28.253719 version@stat F·[4] S·335KiB[335KiB] Sc·[1.00] +10:23:28.256305 db@janitor F·6 G·0 +10:23:28.256317 db@open done T·4.216796ms +10:23:28.256346 table@compaction L0·4 -> L1·0 S·335KiB Q·21863 +10:23:28.262948 table@build created L1@16 N·18180 S·307KiB "\x17\xc7\v..\x0f\x89\xe4,v7242":"\xf2~\xe4..\x05\xa4\xc4,v21827" +10:23:28.263025 version@stat F·[0 1] S·307KiB[0B 307KiB] Sc·[0.00 0.00] +10:23:28.263636 table@compaction committed F-3 S-27KiB Ke·0 D·3679 T·7.276661ms +10:23:28.263708 table@remove removed @8 +10:23:28.263765 table@remove removed @5 +10:23:28.263814 table@remove removed @2 +=============== Jun 10, 2024 (UTC) =============== +10:27:08.425591 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:27:08.425679 version@stat F·[0 1] S·307KiB[0B 307KiB] Sc·[0.00 0.00] +10:27:08.425689 db@open opening +10:27:08.425716 journal@recovery F·1 +10:27:08.425794 journal@recovery recovering @14 +10:27:08.427349 memdb@flush created L0@17 N·1470 S·20KiB "blo..\x01\xc2h,v21864":"blo..k\x00\x01,v23309" +10:27:08.427458 version@stat F·[1 1] S·328KiB[20KiB 307KiB] Sc·[0.25 0.00] +10:27:08.430053 db@janitor F·5 G·1 +10:27:08.430059 db@janitor removing table-13 +10:27:08.430093 db@open done T·4.402037ms +=============== Jun 10, 2024 (UTC) =============== +10:30:28.040468 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +10:30:28.040547 version@stat F·[1 1] S·328KiB[20KiB 307KiB] Sc·[0.25 0.00] +10:30:28.040558 db@open opening +10:30:28.040587 journal@recovery F·1 +10:30:28.042484 journal@recovery recovering @18 +10:30:28.043684 memdb@flush created L0@20 N·684 S·11KiB "blo..\x01\u0092,v23335":"\x8aq\x93..4\x8a\x01,v23563" +10:30:28.043794 version@stat F·[2 1] S·339KiB[31KiB 307KiB] Sc·[0.50 0.00] +10:30:28.047375 db@janitor F·5 G·0 +10:30:28.047387 db@open done T·6.825167ms +=============== Jun 10, 2024 (UTC) =============== +11:11:44.618970 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:11:44.619059 version@stat F·[2 1] S·339KiB[31KiB 307KiB] Sc·[0.50 0.00] +11:11:44.619069 db@open opening +11:11:44.619102 journal@recovery F·1 +11:11:44.619201 journal@recovery recovering @21 +11:11:44.621160 memdb@flush created L0@23 N·1750 S·24KiB "blo..\x01¥,v24020":"blo..k\x00\x01,v25745" +11:11:44.621310 version@stat F·[3 1] S·364KiB[56KiB 307KiB] Sc·[0.75 0.00] +11:11:44.624054 db@janitor F·6 G·0 +11:11:44.624071 db@open done T·4.997421ms +=============== Jun 10, 2024 (UTC) =============== +11:15:00.406082 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:15:00.406168 version@stat F·[3 1] S·364KiB[56KiB 307KiB] Sc·[0.75 0.00] +11:15:00.406179 db@open opening +11:15:00.406218 journal@recovery F·1 +11:15:00.406430 journal@recovery recovering @24 +11:15:00.407797 memdb@flush created L0@26 N·947 S·33KiB "\tr\xf8..pSV,v26111":"\xec.\xac..\x95\xd6=,v26445" +11:15:00.409639 version@stat F·[4 1] S·397KiB[89KiB 307KiB] Sc·[1.00 0.00] +11:15:00.412219 db@janitor F·7 G·0 +11:15:00.412231 db@open done T·6.04823ms +11:15:00.412270 table@compaction L0·4 -> L1·1 S·397KiB Q·26718 +11:15:00.421972 table@build created L1@29 N·22236 S·395KiB "\tr\xf8..pSV,v26111":"\xf2~\xe4..\x05\xa4\xc4,v21827" +11:15:00.421998 version@stat F·[0 1] S·395KiB[0B 395KiB] Sc·[0.00 0.00] +11:15:00.422579 table@compaction committed F-4 S-2KiB Ke·0 D·795 T·10.284235ms +11:15:00.422646 table@remove removed @23 +11:15:00.422683 table@remove removed @20 +11:15:00.422717 table@remove removed @17 +11:15:00.422799 table@remove removed @16 +=============== Jun 10, 2024 (UTC) =============== +11:21:08.228635 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:21:08.228712 version@stat F·[0 1] S·395KiB[0B 395KiB] Sc·[0.00 0.00] +11:21:08.228722 db@open opening +11:21:08.228751 journal@recovery F·1 +11:21:08.229030 journal@recovery recovering @27 +11:21:08.231032 version@stat F·[0 1] S·395KiB[0B 395KiB] Sc·[0.00 0.00] +11:21:08.233701 db@janitor F·4 G·1 +11:21:08.233712 db@janitor removing table-26 +11:21:08.233762 db@open done T·5.036372ms +=============== Jun 10, 2024 (UTC) =============== +11:22:28.294744 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:22:28.294812 version@stat F·[0 1] S·395KiB[0B 395KiB] Sc·[0.00 0.00] +11:22:28.294823 db@open opening +11:22:28.294852 journal@recovery F·1 +11:22:28.295021 journal@recovery recovering @30 +11:22:28.296000 memdb@flush created L0@32 N·35 S·992B "blo..\x01\xc2\xef,v26719":"blo..k\x00\x01,v26729" +11:22:28.296389 version@stat F·[1 1] S·396KiB[992B 395KiB] Sc·[0.25 0.00] +11:22:28.298957 db@janitor F·4 G·0 +11:22:28.298970 db@open done T·4.143054ms +=============== Jun 10, 2024 (UTC) =============== +11:23:15.259916 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:23:15.260005 version@stat F·[1 1] S·396KiB[992B 395KiB] Sc·[0.25 0.00] +11:23:15.260017 db@open opening +11:23:15.260048 journal@recovery F·1 +11:23:15.261938 journal@recovery recovering @33 +11:23:15.262754 memdb@flush created L0@35 N·175 S·2KiB "blo..\x01\xc2\xf0,v26755":"blo..k\x00\x01,v26905" +11:23:15.262866 version@stat F·[2 1] S·399KiB[3KiB 395KiB] Sc·[0.50 0.00] +11:23:15.265329 db@janitor F·5 G·0 +11:23:15.265341 db@open done T·5.319964ms +=============== Jun 10, 2024 (UTC) =============== +11:29:08.634321 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:29:08.634397 version@stat F·[2 1] S·399KiB[3KiB 395KiB] Sc·[0.50 0.00] +11:29:08.634410 db@open opening +11:29:08.634447 journal@recovery F·1 +11:29:08.634524 journal@recovery recovering @36 +11:29:08.636530 memdb@flush created L0@38 N·2310 S·32KiB "blo..\x01\xc2\xf5,v26931":"blo..k\x00\x01,v29216" +11:29:08.636644 version@stat F·[3 1] S·432KiB[36KiB 395KiB] Sc·[0.75 0.00] +11:29:08.639364 db@janitor F·6 G·0 +11:29:08.639377 db@open done T·4.962372ms +=============== Jun 10, 2024 (UTC) =============== +11:30:42.317380 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:42.317458 version@stat F·[3 1] S·432KiB[36KiB 395KiB] Sc·[0.75 0.00] +11:30:42.317471 db@open opening +11:30:42.317506 journal@recovery F·1 +11:30:42.317764 journal@recovery recovering @39 +11:30:42.318864 memdb@flush created L0@41 N·595 S·8KiB "blo..\x01\xc37,v29242":"blo..k\x00\x01,v29812" +11:30:42.320783 version@stat F·[4 1] S·440KiB[45KiB 395KiB] Sc·[1.00 0.00] +11:30:42.323381 db@janitor F·7 G·0 +11:30:42.323395 db@open done T·5.920359ms +11:30:42.323415 table@compaction L0·4 -> L1·1 S·440KiB Q·29837 +11:30:42.332306 table@build created L1@44 N·24817 S·436KiB "\tr\xf8..pSV,v26111":"\xf2~\xe4..\x05\xa4\xc4,v21827" +11:30:42.332329 version@stat F·[0 1] S·436KiB[0B 436KiB] Sc·[0.00 0.00] +11:30:42.338634 table@compaction committed F-4 S-4KiB Ke·0 D·534 T·15.198827ms +11:30:42.338710 table@remove removed @38 +11:30:42.338745 table@remove removed @35 +11:30:42.338775 table@remove removed @32 +11:30:42.338885 table@remove removed @29 +=============== Jun 10, 2024 (UTC) =============== +11:30:55.701766 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:30:55.701846 version@stat F·[0 1] S·436KiB[0B 436KiB] Sc·[0.00 0.00] +11:30:55.701860 db@open opening +11:30:55.701891 journal@recovery F·1 +11:30:55.702099 journal@recovery recovering @42 +11:30:55.703313 memdb@flush created L0@45 N·35 S·989B "blo..\x01\xc3H,v29838":"blo..k\x00\x01,v29848" +11:30:55.703661 version@stat F·[1 1] S·437KiB[989B 436KiB] Sc·[0.25 0.00] +11:30:55.706204 db@janitor F·5 G·1 +11:30:55.706214 db@janitor removing table-41 +11:30:55.706249 db@open done T·4.385017ms +=============== Jun 10, 2024 (UTC) =============== +11:36:36.089192 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +11:36:36.089306 version@stat F·[1 1] S·437KiB[989B 436KiB] Sc·[0.25 0.00] +11:36:36.089320 db@open opening +11:36:36.089352 journal@recovery F·1 +11:36:36.089445 journal@recovery recovering @46 +11:36:36.091405 memdb@flush created L0@48 N·2100 S·29KiB "blo..\x01\xc3I,v29874":"blo..k\x00\x01,v31949" +11:36:36.091532 version@stat F·[2 1] S·467KiB[30KiB 436KiB] Sc·[0.50 0.00] +11:36:36.095700 db@janitor F·5 G·0 +11:36:36.095715 db@open done T·6.390813ms +=============== Jun 10, 2024 (UTC) =============== +12:01:04.975418 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:01:04.975503 version@stat F·[2 1] S·467KiB[30KiB 436KiB] Sc·[0.50 0.00] +12:01:04.975517 db@open opening +12:01:04.975549 journal@recovery F·1 +12:01:04.975645 journal@recovery recovering @49 +12:01:04.977423 memdb@flush created L0@51 N·1750 S·24KiB "blo..\x01Å,v31975":"blo..k\x00\x01,v33700" +12:01:04.977537 version@stat F·[3 1] S·491KiB[55KiB 436KiB] Sc·[0.75 0.00] +12:01:04.980160 db@janitor F·6 G·0 +12:01:04.980173 db@open done T·4.651841ms +=============== Jun 10, 2024 (UTC) =============== +12:02:59.963267 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:02:59.963359 version@stat F·[3 1] S·491KiB[55KiB 436KiB] Sc·[0.75 0.00] +12:02:59.963372 db@open opening +12:02:59.963406 journal@recovery F·1 +12:02:59.963680 journal@recovery recovering @52 +12:02:59.964573 memdb@flush created L0@54 N·105 S·2KiB "blo..\x01÷,v33726":"blo..k\x00\x01,v33806" +12:02:59.966426 version@stat F·[4 1] S·493KiB[57KiB 436KiB] Sc·[1.00 0.00] +12:02:59.969351 db@janitor F·7 G·0 +12:02:59.969369 db@open done T·5.991293ms +12:02:59.969398 table@compaction L0·4 -> L1·1 S·493KiB Q·33831 +12:02:59.980814 table@build created L1@57 N·28123 S·487KiB "\tr\xf8..pSV,v26111":"\xf2~\xe4..\x05\xa4\xc4,v21827" +12:02:59.980843 version@stat F·[0 1] S·487KiB[0B 487KiB] Sc·[0.00 0.00] +12:02:59.981432 table@compaction committed F-4 S-6KiB Ke·0 D·684 T·12.014736ms +12:02:59.981524 table@remove removed @51 +12:02:59.981577 table@remove removed @48 +12:02:59.981619 table@remove removed @45 +12:02:59.981766 table@remove removed @44 +=============== Jun 10, 2024 (UTC) =============== +12:03:19.623990 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:03:19.624078 version@stat F·[0 1] S·487KiB[0B 487KiB] Sc·[0.00 0.00] +12:03:19.624091 db@open opening +12:03:19.624122 journal@recovery F·1 +12:03:19.624431 journal@recovery recovering @55 +12:03:19.625258 memdb@flush created L0@58 N·70 S·1KiB "blo..\x01ú,v33832":"blo..k\x00\x01,v33877" +12:03:19.627206 version@stat F·[1 1] S·488KiB[1KiB 487KiB] Sc·[0.25 0.00] +12:03:19.629930 db@janitor F·5 G·1 +12:03:19.629939 db@janitor removing table-54 +12:03:19.629974 db@open done T·5.877682ms +=============== Jun 10, 2024 (UTC) =============== +12:04:23.554964 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:04:23.555042 version@stat F·[1 1] S·488KiB[1KiB 487KiB] Sc·[0.25 0.00] +12:04:23.555054 db@open opening +12:04:23.555092 journal@recovery F·1 +12:04:23.555194 journal@recovery recovering @59 +12:04:23.555943 memdb@flush created L0@61 N·35 S·986B "blo..\x01ü,v33903":"blo..k\x00\x01,v33913" +12:04:23.556098 version@stat F·[2 1] S·489KiB[2KiB 487KiB] Sc·[0.50 0.00] +12:04:23.558875 db@janitor F·5 G·0 +12:04:23.558888 db@open done T·3.829644ms +=============== Jun 10, 2024 (UTC) =============== +12:07:46.674194 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:07:46.674325 version@stat F·[2 1] S·489KiB[2KiB 487KiB] Sc·[0.50 0.00] +12:07:46.674343 db@open opening +12:07:46.674391 journal@recovery F·1 +12:07:46.676322 journal@recovery recovering @62 +12:07:46.676495 version@stat F·[2 1] S·489KiB[2KiB 487KiB] Sc·[0.50 0.00] +12:07:46.679126 db@janitor F·5 G·0 +12:07:46.679139 db@open done T·4.790762ms +=============== Jun 10, 2024 (UTC) =============== +12:08:02.070916 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:08:02.071015 version@stat F·[2 1] S·489KiB[2KiB 487KiB] Sc·[0.50 0.00] +12:08:02.071028 db@open opening +12:08:02.071062 journal@recovery F·1 +12:08:02.071327 journal@recovery recovering @64 +12:08:02.071493 version@stat F·[2 1] S·489KiB[2KiB 487KiB] Sc·[0.50 0.00] +12:08:02.074133 db@janitor F·5 G·0 +12:08:02.074159 db@open done T·3.125978ms +=============== Jun 10, 2024 (UTC) =============== +12:27:21.428181 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:27:21.428282 version@stat F·[2 1] S·489KiB[2KiB 487KiB] Sc·[0.50 0.00] +12:27:21.428295 db@open opening +12:27:21.428325 journal@recovery F·1 +12:27:21.428582 journal@recovery recovering @66 +12:27:21.428741 version@stat F·[2 1] S·489KiB[2KiB 487KiB] Sc·[0.50 0.00] +12:27:21.431302 db@janitor F·5 G·0 +12:27:21.431312 db@open done T·3.012646ms +=============== Jun 10, 2024 (UTC) =============== +12:56:43.788508 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:56:43.788576 version@stat F·[2 1] S·489KiB[2KiB 487KiB] Sc·[0.50 0.00] +12:56:43.788588 db@open opening +12:56:43.788625 journal@recovery F·1 +12:56:43.788708 journal@recovery recovering @68 +12:56:43.789495 memdb@flush created L0@70 N·70 S·1KiB "blo..\x01ý,v33939":"blo..k\x00\x01,v33984" +12:56:43.789609 version@stat F·[3 1] S·491KiB[3KiB 487KiB] Sc·[0.75 0.00] +12:56:43.793896 db@janitor F·6 G·0 +12:56:43.793908 db@open done T·5.315456ms +=============== Jun 10, 2024 (UTC) =============== +12:57:38.356927 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:57:38.357011 version@stat F·[3 1] S·491KiB[3KiB 487KiB] Sc·[0.75 0.00] +12:57:38.357027 db@open opening +12:57:38.357075 journal@recovery F·1 +12:57:38.357287 journal@recovery recovering @71 +12:57:38.357967 version@stat F·[3 1] S·491KiB[3KiB 487KiB] Sc·[0.75 0.00] +12:57:38.361003 db@janitor F·6 G·0 +12:57:38.361017 db@open done T·3.984844ms +=============== Jun 10, 2024 (UTC) =============== +12:58:01.454825 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:01.454905 version@stat F·[3 1] S·491KiB[3KiB 487KiB] Sc·[0.75 0.00] +12:58:01.454919 db@open opening +12:58:01.454958 journal@recovery F·1 +12:58:01.455047 journal@recovery recovering @73 +12:58:01.455784 memdb@flush created L0@75 N·35 S·985B "blo..\x01ÿ,v34010":"blo..k\x00\x01,v34020" +12:58:01.455897 version@stat F·[4 1] S·492KiB[4KiB 487KiB] Sc·[1.00 0.00] +12:58:01.458473 db@janitor F·7 G·0 +12:58:01.458486 db@open done T·3.560491ms +12:58:01.458514 table@compaction L0·4 -> L1·1 S·492KiB Q·34045 +12:58:01.469129 table@build created L1@78 N·28297 S·490KiB "\tr\xf8..pSV,v26111":"\xf2~\xe4..\x05\xa4\xc4,v21827" +12:58:01.469153 version@stat F·[0 1] S·490KiB[0B 490KiB] Sc·[0.00 0.00] +12:58:01.469731 table@compaction committed F-4 S-2KiB Ke·0 D·36 T·11.200588ms +12:58:01.469799 table@remove removed @70 +12:58:01.469837 table@remove removed @61 +12:58:01.469869 table@remove removed @58 +12:58:01.469990 table@remove removed @57 +=============== Jun 10, 2024 (UTC) =============== +12:58:48.978728 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:58:48.978800 version@stat F·[0 1] S·490KiB[0B 490KiB] Sc·[0.00 0.00] +12:58:48.978816 db@open opening +12:58:48.978849 journal@recovery F·1 +12:58:48.980846 journal@recovery recovering @76 +12:58:48.981597 memdb@flush created L0@79 N·70 S·1KiB "blo..\x01\xc3\xc0,v34046":"blo..k\x00\x01,v34091" +12:58:48.981707 version@stat F·[1 1] S·491KiB[1KiB 490KiB] Sc·[0.25 0.00] +12:58:48.984274 db@janitor F·5 G·1 +12:58:48.984283 db@janitor removing table-75 +12:58:48.984317 db@open done T·5.495648ms +=============== Jun 10, 2024 (UTC) =============== +12:59:05.219764 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:05.219845 version@stat F·[1 1] S·491KiB[1KiB 490KiB] Sc·[0.25 0.00] +12:59:05.219860 db@open opening +12:59:05.219891 journal@recovery F·1 +12:59:05.221853 journal@recovery recovering @80 +12:59:05.223847 version@stat F·[1 1] S·491KiB[1KiB 490KiB] Sc·[0.25 0.00] +12:59:05.226424 db@janitor F·4 G·0 +12:59:05.226442 db@open done T·6.577477ms +=============== Jun 10, 2024 (UTC) =============== +12:59:32.405516 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +12:59:32.405608 version@stat F·[1 1] S·491KiB[1KiB 490KiB] Sc·[0.25 0.00] +12:59:32.405621 db@open opening +12:59:32.405666 journal@recovery F·1 +12:59:32.405757 journal@recovery recovering @82 +12:59:32.406528 memdb@flush created L0@84 N·35 S·989B "blo..\x01\xc3\xc2,v34117":"blo..k\x00\x01,v34127" +12:59:32.406659 version@stat F·[2 1] S·492KiB[2KiB 490KiB] Sc·[0.50 0.00] +12:59:32.411831 db@janitor F·5 G·0 +12:59:32.411845 db@open done T·6.219235ms +=============== Jul 3, 2024 (UTC) =============== +17:52:58.323595 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:52:58.323693 version@stat F·[2 1] S·492KiB[2KiB 490KiB] Sc·[0.50 0.00] +17:52:58.323706 db@open opening +17:52:58.323743 journal@recovery F·1 +17:52:58.323963 journal@recovery recovering @85 +17:52:58.325969 version@stat F·[2 1] S·492KiB[2KiB 490KiB] Sc·[0.50 0.00] +17:52:58.328526 db@janitor F·5 G·0 +17:52:58.328541 db@open done T·4.8287ms +=============== Jul 3, 2024 (UTC) =============== +17:59:01.705778 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +17:59:01.705947 version@stat F·[2 1] S·492KiB[2KiB 490KiB] Sc·[0.50 0.00] +17:59:01.705959 db@open opening +17:59:01.705991 journal@recovery F·1 +17:59:01.706077 journal@recovery recovering @87 +17:59:01.708294 memdb@flush created L0@89 N·2541 S·58KiB "\x1c\xa7\x1b..\x9a\xa4\xe0,v36448":"\x85\x00\xda..\x95K\xe3,v36113" +17:59:01.708419 version@stat F·[3 1] S·550KiB[60KiB 490KiB] Sc·[0.75 0.00] +17:59:01.712571 db@janitor F·6 G·0 +17:59:01.712584 db@open done T·6.619955ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.953178 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.953252 version@stat F·[3 1] S·550KiB[60KiB 490KiB] Sc·[0.75 0.00] +05:10:40.953263 db@open opening +05:10:40.953304 journal@recovery F·1 +05:10:40.953387 journal@recovery recovering @90 +05:10:40.954217 memdb@flush created L0@92 N·175 S·2KiB "blo..\x01\xc4\b,v36695":"blo..k\x00\x01,v36845" +05:10:40.954329 version@stat F·[4 1] S·553KiB[63KiB 490KiB] Sc·[1.00 0.00] +05:10:40.956867 db@janitor F·7 G·0 +05:10:40.956878 db@open done T·3.611318ms +05:10:40.956914 table@compaction L0·4 -> L1·1 S·553KiB Q·36870 +05:10:40.970187 table@build created L1@95 N·30671 S·553KiB "\tr\xf8..pSV,v26111":"\xf2~\xe4..\x05\xa4\xc4,v21827" +05:10:40.970217 version@stat F·[0 1] S·553KiB[0B 553KiB] Sc·[0.00 0.01] +05:10:40.970807 table@compaction committed F-4 S-550B Ke·0 D·447 T·13.871697ms +05:10:40.970885 table@remove removed @89 +05:10:40.970918 table@remove removed @84 +05:10:40.970943 table@remove removed @79 +05:10:40.971049 table@remove removed @78 +=============== Oct 17, 2024 (UTC) =============== +06:44:58.783278 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.783368 version@stat F·[0 1] S·553KiB[0B 553KiB] Sc·[0.00 0.01] +06:44:58.783380 db@open opening +06:44:58.783411 journal@recovery F·1 +06:44:58.783501 journal@recovery recovering @93 +06:44:58.792589 memdb@flush created L0@96 N·5227 S·104KiB "\t\xf8|..\"=\xa9,v39810":"\x95\xbat..\xc7FU,v40268" +06:44:58.792706 version@stat F·[1 1] S·657KiB[104KiB 553KiB] Sc·[0.25 0.01] +06:44:58.802828 db@janitor F·5 G·1 +06:44:58.802834 db@janitor removing table-92 +06:44:58.802858 db@open done T·19.474428ms +=============== Oct 17, 2024 (UTC) =============== +06:45:46.677216 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.677292 version@stat F·[1 1] S·657KiB[104KiB 553KiB] Sc·[0.25 0.01] +06:45:46.677310 db@open opening +06:45:46.677340 journal@recovery F·1 +06:45:46.677425 journal@recovery recovering @97 +06:45:46.678375 memdb@flush created L0@99 N·302 S·14KiB "\\\x7f\xf1..AP\b,v42182":"\xbf\x87\xea..]\x96\xf3,v42260" +06:45:46.678538 version@stat F·[2 1] S·671KiB[118KiB 553KiB] Sc·[0.50 0.01] +06:45:46.687729 db@janitor F·5 G·0 +06:45:46.687742 db@open done T·10.428559ms +=============== Oct 17, 2024 (UTC) =============== +06:47:26.300462 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.300551 version@stat F·[2 1] S·671KiB[118KiB 553KiB] Sc·[0.50 0.01] +06:47:26.300565 db@open opening +06:47:26.300606 journal@recovery F·1 +06:47:26.300688 journal@recovery recovering @100 +06:47:26.301782 memdb@flush created L0@102 N·525 S·7KiB "blo..\x01Ĥ,v42402":"blo..k\x00\x01,v42902" +06:47:26.301919 version@stat F·[3 1] S·678KiB[125KiB 553KiB] Sc·[0.75 0.01] +06:47:26.306059 db@janitor F·6 G·0 +06:47:26.306075 db@open done T·5.504547ms diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/MANIFEST-000104 b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/MANIFEST-000104 new file mode 100644 index 0000000000..57f27cbda8 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/MANIFEST-000104 differ diff --git a/.docker/container-state/nucleus-testnet-data/keyring-test/224884bc43a4345c66051befe55c0c54676008b3.address b/.docker/container-state/nucleus-testnet-data/keyring-test/224884bc43a4345c66051befe55c0c54676008b3.address new file mode 100644 index 0000000000..94bb305001 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/keyring-test/224884bc43a4345c66051befe55c0c54676008b3.address @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wNi0wNCAxMzowOToyMS42Mzk3ODAzNzUgKzAzMDAgKzAzIG09KzAuMDM4NzA5ODE1IiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoiS2ZkaHFCNGJUUjVWT2ZZZyJ9.mUCckjmMF6wtTNjM5kNeXGYNCBVmZCT0HmHLloJNhIXAn5rws_mi0A.aj5fZAGdGl57m-7v.zsLX47A302loDj0lK94kh7xTVlC2sq_Ry4juzGZf3rE8KyDBx-SI_kNbkMfm6aVKrxZpnxdTmdhPhj8M3ayX7BqW7EjuKjSlLFwkkjJyT4hS4B2_uy5AP-ljUp5x_0SWf96pFPSHZjTT5ilN84jmRJdoiYHvcJtW1HwjzjHfGXUj33T1UqOFU0IoELChE-FKb11TfsIf-d52J8XLcVpw6Rxv__RWB-vxm7dyvQLDZSFrOA.lM0bzqqyCCkurzSN8RR_Vw \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/keyring-test/a36b04d45a23d962b67cd9c82b3065c45c0a80e5.address b/.docker/container-state/nucleus-testnet-data/keyring-test/a36b04d45a23d962b67cd9c82b3065c45c0a80e5.address new file mode 100644 index 0000000000..978c76fe1b --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/keyring-test/a36b04d45a23d962b67cd9c82b3065c45c0a80e5.address @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wNi0wNCAxMzowOToyMS41OTA4MTk0MjggKzAzMDAgKzAzIG09KzAuMDM4MDM1Mzg5IiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoiX3BkeDU2N0hPNVExZU9oZSJ9.hjs12iz7OwHZNtX4IRoX2J7bxJKZ4ZdUwNYU3HMiQsQianl-H8lJZA.bwpDKReJ9-id82O8.fE3UOdwIdY9v6RLeBuDdOVIrDuYR2nA_5opEZ9WQNiuUfgHiOnP4l0u3U9t_PUYujKd6keq9_FgTa1XVdau1odWhfwpXrYDZgMw63PaSf8OSxezmBgjYklSeVbJEnThy1A_u2Ie72SK632dWzmfdBRxHRW8tTv4HROrCOEFn3iNQdhZS82xU2b6uq2Nl9h_euzt1ddGLWy5OhtEwivI7jalpKy43_G8pHwrifartXR3PKXEBYEI.TtEeC1L4Nio85Emo_9UrHQ \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/keyring-test/alice.info b/.docker/container-state/nucleus-testnet-data/keyring-test/alice.info new file mode 100644 index 0000000000..f8c106187b --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/keyring-test/alice.info @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wNi0wNCAxMzowOToyMS41ODczMjk0OSArMDMwMCArMDMgbT0rMC4wMzQ1NDU0NTEiLCJlbmMiOiJBMjU2R0NNIiwicDJjIjo4MTkyLCJwMnMiOiJTYkpvLUNBV0F5YXl6VUppIn0.A4qJ3OdE7CcD4ovRhGXw0jELmfD41e6CEtsTSMoLaL5jBWpENwGXEg.OO53V2D2KZgvhxDG.MPmzfeTKAALGeDUryBGi-mBnj_pWWD-HOK6mx57Ue4uhLmTXaJhmj5ol13o8Xa_nBEs7DY6L8HGo28wPkLIPNLEhhXQ5Qaes9S-TVrcphTJ1K2sf07CrNVXso3R9uU0V_lKkz765Ar4Iuf5vxOft84aou_6qSD5BDtnDSV0RdbnEXDVXuL5acadUMBPBwuddV9NNbHH_uqFVx67CDUJXqDs-XFPVqElGcfuzwtFEQHw2Od6u5TyQE6_xphiidoQoyXDOBbQfQOBtuXlCUvLtClGDXotoyx8FJM5Ns5gpgCKq8Rp8oMpiNOnrt74ag0xGwXFhy6hD8WIlJCNcJZ0UV0KcuL7JO6WSvmHtI1XWoA82RFP6I2P2UleJJsXEVtb44UC2pz0MuhuCA--79yTcdYkkIA5z5fKm4AVwLFAZySbnilIRWABawkI8jSE.QDha2U3XXGLpYDpBOvY2DQ \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/keyring-test/bob.info b/.docker/container-state/nucleus-testnet-data/keyring-test/bob.info new file mode 100644 index 0000000000..04ca589cb8 --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/keyring-test/bob.info @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wNi0wNCAxMzowOToyMS42MzYwOTQyNjUgKzAzMDAgKzAzIG09KzAuMDM1MDIzNjk1IiwiZW5jIjoiQTI1NkdDTSIsInAyYyI6ODE5MiwicDJzIjoickljRWhKdXhUdmlQQklYaiJ9.f2yI5y6rWVCBiXAJ2pe7gNAKApYpF47FIU0J7GDzxHM-RJ1uZB_GhQ.TzUGK_soy1SV8Z4a.ROxRJJdi8_2CrMhR7LCG9OaJJvyAF5fu_m_sF-aUAwvxC47IgAGodzKPuw_JCbg6xMmRF0t36vUiuyC5690DOOc_YjVS0t7ZzLt0_7dC-FvLF3Wz8PrJH1h1LZ4PWTAwLymNqBDGoLaM72K9xWNV3iUbxdBYlYfCLGH5bNg3TsIkm4sOcsm0gPZptaNdxYMfkX8iXiyOruaELUq3hJoqN3Qbz6UnEpZ1vZzjOKTm5XK4hxOEuoAq75D-GmjmA80vqiHHiTrARhQQTeMw2IpuUpkd4MIKecyf0JlJ_pGKOjZ5pIw0AGns7NEOqLJlYUE_EldWSUSSq4-FPu2uGMsKh2g4cSoXa4bqW0scYm28h_IW9rFzfPIU-L_pGTrziAmWiDJjcbS2ml-xl3uUid6nM34CeN1rqbn18zc54BBADGmM4U8Oty9ucNK1.NE7JvnAisuaiVQqJZbvUow \ No newline at end of file diff --git a/.dockerignore b/.dockerignore index 5e4e3785e1..778ad13875 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,6 +6,8 @@ cmake-build-debug !/target/release/mm2 !/target/debug/mm2 +!/target/release/kdf +!/target/debug/kdf /mm2src/*/target /build @@ -17,6 +19,8 @@ cmake-build-debug /js/*.wasm /js/mm2 /js/mm2.exe +/js/kdf +/js/kdf.exe /wasm-build.log # Opt out from history in order to speed the `COPY .` up. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 440838e30b..9d788ecc55 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: Marketmaker 1.0 bug report +about: Komodo DeFi Framework bug report --- @@ -9,10 +9,10 @@ A clear and concise description of what the bug is. **Please answer following questions and attach requested info - it'll help to solve issue faster** - What OS do you use? -- What marketmaker version do you run? +- What KDF version do you run? - Attach your coins.json config. - Provide your enable script with response. - Provide other curl scripts (with responses) which were executed prior to error. -- Attach full marketmaker console logs (start collecting right after marketmaker execution). +- Attach full KDF console logs (start collecting right after KDF execution). - ***Make sure that you don't send your passphrase, userpass and privkeys. Your funds might be stolen if you reveal this info publicly!*** - Provide info for all nodes involved (e.g. if error occurs during atomic swap you should provide info for both Bob and Alice). diff --git a/.github/actions/deps-install/action.yml b/.github/actions/deps-install/action.yml index bc87d7c418..02b0ffbec4 100644 --- a/.github/actions/deps-install/action.yml +++ b/.github/actions/deps-install/action.yml @@ -12,33 +12,54 @@ inputs: runs: using: 'composite' steps: + - name: Download protoc (Linux) + if: runner.os == 'Linux' && contains(inputs.deps, 'protoc') + uses: ./.github/actions/download-and-verify + with: + url: "https://github.com/protocolbuffers/protobuf/releases/download/v25.3/protoc-25.3-linux-x86_64.zip" + output_file: "protoc-25.3-linux-x86_64.zip" + checksum: "f853e691868d0557425ea290bf7ba6384eef2fa9b04c323afab49a770ba9da80" + - name: Install protoc (Linux) env: TMP: ${{ inputs.temp || runner.temp }} if: runner.os == 'Linux' && contains(inputs.deps, 'protoc') shell: bash run: | - wget https://github.com/protocolbuffers/protobuf/releases/download/v25.3/protoc-25.3-linux-x86_64.zip - unzip protoc-25.3-linux-x86_64 -d "$TMP/protobuf" + unzip protoc-25.3-linux-x86_64.zip -d "$TMP/protobuf" echo "$TMP/protobuf/bin" >> $GITHUB_PATH + - name: Download protoc (MacOS) + if: runner.os == 'macOS' && contains(inputs.deps, 'protoc') + uses: ./.github/actions/download-and-verify + with: + url: "https://github.com/protocolbuffers/protobuf/releases/download/v25.3/protoc-25.3-osx-x86_64.zip" + output_file: "protoc-25.3-osx-x86_64.zip" + checksum: "247e003b8e115405172eacc50bd19825209d85940728e766f0848eee7c80e2a1" + - name: Install protoc (MacOS) env: TMP: ${{ inputs.temp || runner.temp }} if: runner.os == 'macOS' && contains(inputs.deps, 'protoc') shell: bash run: | - wget https://github.com/protocolbuffers/protobuf/releases/download/v25.3/protoc-25.3-osx-x86_64.zip unzip protoc-25.3-osx-x86_64.zip -d "$TMP/protobuf" echo "$TMP/protobuf/bin" >> $GITHUB_PATH + + - name: Download protoc (Windows) + uses: ./.github/actions/download-and-verify + with: + url: "https://github.com/protocolbuffers/protobuf/releases/download/v25.3/protoc-25.3-win64.zip" + output_file: "protoc-25.3-win64.zip" + checksum: "d6b336b852726364313330631656b7f395dde5b1141b169f5c4b8d43cdf01482" + - name: Install protoc (Windows) env: TMP: ${{ inputs.temp || runner.temp }} if: runner.os == 'Windows' && contains(inputs.deps, 'protoc') shell: powershell run: | - Invoke-WebRequest -Uri https://github.com/protocolbuffers/protobuf/releases/download/v25.3/protoc-25.3-win64.zip -OutFile protoc-25.3-win64.zip 7z x protoc-25.3-win64.zip -o"$TMP\protobuf" echo "$TMP\protobuf\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append @@ -48,3 +69,14 @@ runs: run: | sudo apt-get update -y sudo apt-get install -y libudev-dev + + - name: Install python3 + uses: actions/setup-python@v5 + if: contains(inputs.deps, 'python3') + with: + python-version: 3 + + - name: Install `paramiko` pip package + if: contains(inputs.deps, 'paramiko') + shell: bash + run: pip install paramiko diff --git a/.github/actions/download-and-verify/action.yml b/.github/actions/download-and-verify/action.yml new file mode 100644 index 0000000000..198601f486 --- /dev/null +++ b/.github/actions/download-and-verify/action.yml @@ -0,0 +1,46 @@ +name: "Download and verify remote files" + +runs: + using: "composite" + steps: + - name: Download (Unix) + if: runner.os != 'Windows' + shell: bash + run: curl -L -o ${{ inputs.output_file }} ${{ inputs.url }} + + - name: Download (Windows) + if: runner.os == 'Windows' + shell: powershell + run: Invoke-WebRequest -Uri ${{ inputs.url }} -OutFile ${{ inputs.output_file }} + + - name: Verify (Unix) + if: runner.os != 'Windows' + shell: bash + run: | + if [[ "$RUNNER_OS" == "macOS" ]]; then + echo "${{ inputs.checksum }} *${{ inputs.output_file }}" | shasum -a 256 -c + else + echo "${{ inputs.checksum }} ${{ inputs.output_file }}" | sha256sum -c + fi + + - name: Verify (Windows) + if: runner.os == 'Windows' + shell: powershell + run: | + $expectedChecksum = "${{ inputs.checksum }}" + $actualChecksum = (Get-FileHash -Path "${{ inputs.output_file }}" -Algorithm SHA256).Hash + if ($expectedChecksum -ne $actualChecksum) { + Write-Output "Checksum did not match! Expected: $expectedChecksum, Found: $actualChecksum" + exit 1 + } + +inputs: + url: + description: "URL of the remote file." + required: true + output_file: + description: "Output path." + required: true + checksum: + description: "Expected checksum of the downloaded file." + required: true diff --git a/.github/workflows/adex-cli.yml b/.github/workflows/adex-cli.yml deleted file mode 100644 index 32b62f04ed..0000000000 --- a/.github/workflows/adex-cli.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Adex CLI -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -env: - BRANCH_NAME: ${{ github.head_ref || github.ref_name }} - -jobs: - code-check: - name: Code Checks - timeout-minutes: 60 - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - steps: - - uses: actions/checkout@v3 - - - name: Cargo cache - uses: ./.github/actions/cargo-cache - - - name: Start checking code format and lint - continue-on-error: true - run: | - cargo fmt --manifest-path ./mm2src/adex_cli/Cargo.toml --all -- --check - cargo clippy --manifest-path ./mm2src/adex_cli/Cargo.toml --all-targets --all-features -- --D warnings - - - name: Start building - run: | - cargo build --manifest-path ./mm2src/adex_cli/Cargo.toml - - - name: Start testing - run: | - cargo test --manifest-path ./mm2src/adex_cli/Cargo.toml --no-fail-fast diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index b946538d99..a4fce83cbe 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -30,8 +30,8 @@ jobs: - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 - name: Install build deps uses: ./.github/actions/deps-install @@ -53,9 +53,9 @@ jobs: run: | rm -f ./MM_VERSION echo $COMMIT_HASH > ./MM_VERSION - cargo build --bin mm2 --release + cargo build --release - - name: Compress build output + - name: Compress mm2 build output env: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} @@ -65,18 +65,23 @@ jobs: mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - - name: Upload output + - name: Compress kdf build output env: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} - uses: garygrossgarten/github-action-scp@v0.8.0 - with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + run: | + NAME="kdf_$COMMIT_HASH-linux-x86-64.zip" + zip $NAME target/release/kdf -j + mv $NAME ./$BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" - name: Login to dockerhub if: github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' @@ -97,14 +102,14 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 rustup target add x86_64-apple-darwin - name: Install build deps uses: ./.github/actions/deps-install with: - deps: ('protoc') + deps: ('protoc', 'python3', 'paramiko') - name: Calculate commit hash for PR commit if: github.event_name == 'pull_request' @@ -121,9 +126,9 @@ jobs: run: | rm -f ./MM_VERSION echo $COMMIT_HASH > ./MM_VERSION - cargo build --bin mm2 --release --target x86_64-apple-darwin + cargo build --release --target x86_64-apple-darwin - - name: Compress build output + - name: Compress mm2 build output env: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} @@ -133,18 +138,84 @@ jobs: mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - - name: Upload output + - name: Compress kdf build output env: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} - uses: garygrossgarten/github-action-scp@v0.8.0 + run: | + NAME="kdf_$COMMIT_HASH-mac-x86-64.zip" + zip $NAME target/x86_64-apple-darwin/release/kdf -j + mv $NAME ./$BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" + + mac-arm64: + timeout-minutes: 60 + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 + rustup target add aarch64-apple-darwin + + - name: Install build deps + uses: ./.github/actions/deps-install with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + deps: ('protoc', 'python3', 'paramiko') + + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + + - name: Cargo cache + uses: ./.github/actions/cargo-cache + + - name: Build + run: | + rm -f ./MM_VERSION + echo $COMMIT_HASH > ./MM_VERSION + cargo build --release --target aarch64-apple-darwin + + - name: Compress mm2 build output + env: + AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.AVAILABLE != '' }} + run: | + NAME="mm2_$COMMIT_HASH-mac-arm64.zip" + zip $NAME target/aarch64-apple-darwin/release/mm2 -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Compress kdf build output + env: + AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.AVAILABLE != '' }} + run: | + NAME="kdf_$COMMIT_HASH-mac-arm64.zip" + zip $NAME target/aarch64-apple-darwin/release/kdf -j + mv $NAME ./$BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" win-x86-64: timeout-minutes: 60 @@ -153,13 +224,13 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 - name: Install build deps uses: ./.github/actions/deps-install with: - deps: ('protoc') + deps: ('protoc', 'python3', 'paramiko') - name: Calculate commit hash for PR commit if: github.event_name == 'pull_request' @@ -178,9 +249,9 @@ jobs: remove-item "./MM_VERSION" } echo $Env:COMMIT_HASH > ./MM_VERSION - cargo build --bin mm2 --release + cargo build --release - - name: Compress build output + - name: Compress mm2 build output env: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} @@ -190,18 +261,23 @@ jobs: mkdir $Env:BRANCH_NAME mv $NAME ./$Env:BRANCH_NAME/ - - name: Upload output + - name: Compress kdf build output env: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} - uses: garygrossgarten/github-action-scp@v0.8.0 - with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + run: | + $NAME="kdf_$Env:COMMIT_HASH-win-x86-64.zip" + 7z a $NAME .\target\release\kdf.exe .\target\release\*.dll + mv $NAME ./$Env:BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" mac-dylib-x86-64: timeout-minutes: 60 @@ -210,14 +286,14 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29-x86_64-apple-darwin --no-self-update --profile=minimal - rustup default nightly-2022-10-29-x86_64-apple-darwin + rustup toolchain install nightly-2023-06-01-x86_64-apple-darwin --no-self-update --profile=minimal + rustup default nightly-2023-06-01-x86_64-apple-darwin rustup target add x86_64-apple-darwin - name: Install build deps uses: ./.github/actions/deps-install with: - deps: ('protoc') + deps: ('protoc', 'python3', 'paramiko') - name: Calculate commit hash for PR commit if: github.event_name == 'pull_request' @@ -236,29 +312,35 @@ jobs: echo $COMMIT_HASH > ./MM_VERSION cargo rustc --target x86_64-apple-darwin --lib --release --package mm2_bin_lib --crate-type=staticlib - - name: Compress build output + - name: Compress mm2 build output env: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} run: | NAME="mm2_$COMMIT_HASH-mac-dylib-x86-64.zip" - mv target/x86_64-apple-darwin/release/libmm2lib.a target/x86_64-apple-darwin/release/libmm2.a + cp target/x86_64-apple-darwin/release/libkdflib.a target/x86_64-apple-darwin/release/libmm2.a zip $NAME target/x86_64-apple-darwin/release/libmm2.a -j mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - - name: Upload output + - name: Compress kdf build output env: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} - uses: garygrossgarten/github-action-scp@v0.8.0 - with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + run: | + NAME="kdf_$COMMIT_HASH-mac-dylib-x86-64.zip" + mv target/x86_64-apple-darwin/release/libkdflib.a target/x86_64-apple-darwin/release/libkdf.a + zip $NAME target/x86_64-apple-darwin/release/libkdf.a -j + mv $NAME ./$BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" wasm: timeout-minutes: 60 @@ -281,8 +363,8 @@ jobs: - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 rustup target add wasm32-unknown-unknown - name: Install wasm-pack @@ -310,23 +392,19 @@ jobs: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} run: | - NAME="mm2_$COMMIT_HASH-wasm.zip" + NAME="kdf_$COMMIT_HASH-wasm.zip" (cd ./target/target-wasm-release && zip -r - .) > $NAME mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - - name: Upload output + - name: Upload build artifact env: - AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} - if: ${{ env.AVAILABLE != '' }} - uses: garygrossgarten/github-action-scp@v0.8.0 - with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" ios-aarch64: timeout-minutes: 60 @@ -335,14 +413,14 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 rustup target add aarch64-apple-ios - name: Install build deps uses: ./.github/actions/deps-install with: - deps: ('protoc') + deps: ('protoc', 'python3', 'paramiko') - name: Calculate commit hash for PR commit if: github.event_name == 'pull_request' @@ -361,29 +439,35 @@ jobs: echo $COMMIT_HASH > ./MM_VERSION cargo rustc --target aarch64-apple-ios --lib --release --package mm2_bin_lib --crate-type=staticlib - - name: Compress build output + - name: Compress mm2 build output env: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} run: | NAME="mm2_$COMMIT_HASH-ios-aarch64.zip" - mv target/aarch64-apple-ios/release/libmm2lib.a target/aarch64-apple-ios/release/libmm2.a + cp target/aarch64-apple-ios/release/libkdflib.a target/aarch64-apple-ios/release/libmm2.a zip $NAME target/aarch64-apple-ios/release/libmm2.a -j mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - - name: Upload output + - name: Compress kdf build output env: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} - uses: garygrossgarten/github-action-scp@v0.8.0 - with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + run: | + NAME="kdf_$COMMIT_HASH-ios-aarch64.zip" + mv target/aarch64-apple-ios/release/libkdflib.a target/aarch64-apple-ios/release/libkdf.a + zip $NAME target/aarch64-apple-ios/release/libkdf.a -j + mv $NAME ./$BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" android-aarch64: timeout-minutes: 60 @@ -401,8 +485,8 @@ jobs: - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 rustup target add aarch64-linux-android - name: Install build deps @@ -432,29 +516,35 @@ jobs: export PATH=$PATH:/android-ndk/bin CC_aarch64_linux_android=aarch64-linux-android21-clang CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android21-clang cargo rustc --target=aarch64-linux-android --lib --release --crate-type=staticlib --package mm2_bin_lib - - name: Compress build output + - name: Compress mm2 build output env: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} run: | NAME="mm2_$COMMIT_HASH-android-aarch64.zip" - mv target/aarch64-linux-android/release/libmm2lib.a target/aarch64-linux-android/release/libmm2.a + cp target/aarch64-linux-android/release/libkdflib.a target/aarch64-linux-android/release/libmm2.a zip $NAME target/aarch64-linux-android/release/libmm2.a -j mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - - name: Upload output + - name: Compress kdf build output env: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} - uses: garygrossgarten/github-action-scp@v0.8.0 - with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + run: | + NAME="kdf_$COMMIT_HASH-android-aarch64.zip" + mv target/aarch64-linux-android/release/libkdflib.a target/aarch64-linux-android/release/libkdf.a + zip $NAME target/aarch64-linux-android/release/libkdf.a -j + mv $NAME ./$BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" android-armv7: timeout-minutes: 60 @@ -472,8 +562,8 @@ jobs: - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 rustup target add armv7-linux-androideabi - name: Install build deps @@ -503,29 +593,35 @@ jobs: export PATH=$PATH:/android-ndk/bin CC_armv7_linux_androideabi=armv7a-linux-androideabi21-clang CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi21-clang cargo rustc --target=armv7-linux-androideabi --lib --release --crate-type=staticlib --package mm2_bin_lib - - name: Compress build output + - name: Compress mm2 build output env: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} run: | NAME="mm2_$COMMIT_HASH-android-armv7.zip" - mv target/armv7-linux-androideabi/release/libmm2lib.a target/armv7-linux-androideabi/release/libmm2.a + cp target/armv7-linux-androideabi/release/libkdflib.a target/armv7-linux-androideabi/release/libmm2.a zip $NAME target/armv7-linux-androideabi/release/libmm2.a -j mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - - name: Upload output + - name: Compress kdf build output env: AVAILABLE: ${{ secrets.FILE_SERVER_KEY }} if: ${{ env.AVAILABLE != '' }} - uses: garygrossgarten/github-action-scp@v0.8.0 - with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + run: | + NAME="kdf_$COMMIT_HASH-android-armv7.zip" + mv target/armv7-linux-androideabi/release/libkdflib.a target/armv7-linux-androideabi/release/libkdf.a + zip $NAME target/armv7-linux-androideabi/release/libkdf.a -j + mv $NAME ./$BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" deployment-commitment: if: github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' diff --git a/.github/workflows/fmt-and-lint.yml b/.github/workflows/fmt-and-lint.yml index 14b5c7e8fd..f5ea217eee 100644 --- a/.github/workflows/fmt-and-lint.yml +++ b/.github/workflows/fmt-and-lint.yml @@ -18,8 +18,8 @@ jobs: - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal --component rustfmt clippy - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal --component rustfmt clippy + rustup default nightly-2023-06-01 - name: Install build deps uses: ./.github/actions/deps-install @@ -45,8 +45,8 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal --component clippy - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal --component clippy + rustup default nightly-2023-06-01 rustup target add wasm32-unknown-unknown - name: Install build deps diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index 59e176a5e0..704c614549 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -22,6 +22,7 @@ jobs: types: | feat fix + improvement chore docs deps @@ -38,9 +39,9 @@ jobs: TITLE: ${{ github.event.pull_request.title }} run: | title_length=${#TITLE} - if [ $title_length -gt 72 ] + if [ $title_length -gt 85 ] then - echo "PR title is too long (greater than 72 characters)" + echo "PR title is too long (greater than 85 characters)" exit 1 fi diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 725b56374c..a74a589d10 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -30,8 +30,8 @@ jobs: - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 - name: Install build deps uses: ./.github/actions/deps-install @@ -53,31 +53,36 @@ jobs: run: | rm -f ./MM_VERSION echo $COMMIT_HASH > ./MM_VERSION - cargo build --bin mm2 --release + cargo build --release - - name: Compress build output + - name: Compress mm2 build output run: | NAME="mm2_$COMMIT_HASH-linux-x86-64.zip" zip $NAME target/release/mm2 -j mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - - name: Upload output - uses: garygrossgarten/github-action-scp@v0.8.0 - with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + - name: Compress kdf build output + run: | + NAME="kdf_$COMMIT_HASH-linux-x86-64.zip" + zip $NAME target/release/kdf -j + mv $NAME ./$BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" - name: Login to dockerhub run: docker login --username ${{ secrets.DOCKER_HUB_USERNAME }} --password ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} docker.io - name: Build and push container image run: | - export CONTAINER_TAG=$(./target/release/mm2 --version | awk '{print $3}') + export CONTAINER_TAG=$(./target/release/kdf --version | awk '{print $3}') docker build -t komodoofficial/komodo-defi-framework:"$CONTAINER_TAG" -t komodoofficial/komodo-defi-framework:main-latest -f .docker/Dockerfile.release . docker push komodoofficial/komodo-defi-framework:"$CONTAINER_TAG" docker push komodoofficial/komodo-defi-framework:main-latest @@ -89,13 +94,13 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 - name: Install build deps uses: ./.github/actions/deps-install with: - deps: ('protoc') + deps: ('protoc', 'python3', 'paramiko') - name: Calculate commit hash for PR commit if: github.event_name == 'pull_request' @@ -112,24 +117,84 @@ jobs: run: | rm -f ./MM_VERSION echo $COMMIT_HASH > ./MM_VERSION - cargo build --bin mm2 --release --target x86_64-apple-darwin + cargo build --release --target x86_64-apple-darwin - - name: Compress build output + - name: Compress mm2 build output run: | NAME="mm2_$COMMIT_HASH-mac-x86-64.zip" zip $NAME target/x86_64-apple-darwin/release/mm2 -j mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - - name: Upload output - uses: garygrossgarten/github-action-scp@v0.8.0 + - name: Compress kdf build output + run: | + NAME="kdf_$COMMIT_HASH-mac-x86-64.zip" + zip $NAME target/x86_64-apple-darwin/release/kdf -j + mv $NAME ./$BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" + + mac-arm64: + timeout-minutes: 60 + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 + rustup target add aarch64-apple-darwin + + - name: Install build deps + uses: ./.github/actions/deps-install with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + deps: ('protoc', 'python3', 'paramiko') + + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + + - name: Cargo cache + uses: ./.github/actions/cargo-cache + + - name: Build + run: | + rm -f ./MM_VERSION + echo $COMMIT_HASH > ./MM_VERSION + cargo build --release --target aarch64-apple-darwin + + - name: Compress mm2 build output + run: | + NAME="mm2_$COMMIT_HASH-mac-arm64.zip" + zip $NAME target/aarch64-apple-darwin/release/mm2 -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Compress kdf build output + run: | + NAME="kdf_$COMMIT_HASH-mac-arm64.zip" + zip $NAME target/aarch64-apple-darwin/release/kdf -j + mv $NAME ./$BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" win-x86-64: timeout-minutes: 60 @@ -138,13 +203,13 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 - name: Install build deps uses: ./.github/actions/deps-install with: - deps: ('protoc') + deps: ('protoc', 'python3', 'paramiko') - name: Calculate commit hash for PR commit if: github.event_name == 'pull_request' @@ -163,24 +228,29 @@ jobs: remove-item "./MM_VERSION" } echo $Env:COMMIT_HASH > ./MM_VERSION - cargo build --bin mm2 --release + cargo build --release - - name: Compress build output + - name: Compress mm2 build output run: | $NAME="mm2_$Env:COMMIT_HASH-win-x86-64.zip" 7z a $NAME .\target\release\mm2.exe .\target\release\*.dll mkdir $Env:BRANCH_NAME mv $NAME ./$Env:BRANCH_NAME/ - - name: Upload output - uses: garygrossgarten/github-action-scp@v0.8.0 - with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + - name: Compress kdf build output + run: | + $NAME="kdf_$Env:COMMIT_HASH-win-x86-64.zip" + 7z a $NAME .\target\release\kdf.exe .\target\release\*.dll + mv $NAME ./$Env:BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" mac-dylib-x86-64: timeout-minutes: 60 @@ -189,13 +259,13 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 - name: Install build deps uses: ./.github/actions/deps-install with: - deps: ('protoc') + deps: ('protoc', 'python3', 'paramiko') - name: Calculate commit hash for PR commit if: github.event_name == 'pull_request' @@ -214,23 +284,29 @@ jobs: echo $COMMIT_HASH > ./MM_VERSION cargo rustc --target x86_64-apple-darwin --lib --release --package mm2_bin_lib --crate-type=staticlib - - name: Compress build output + - name: Compress mm2 build output run: | NAME="mm2_$COMMIT_HASH-mac-dylib-x86-64.zip" - mv target/x86_64-apple-darwin/release/libmm2lib.a target/x86_64-apple-darwin/release/libmm2.a + cp target/x86_64-apple-darwin/release/libkdflib.a target/x86_64-apple-darwin/release/libmm2.a zip $NAME target/x86_64-apple-darwin/release/libmm2.a -j mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - - name: Upload output - uses: garygrossgarten/github-action-scp@v0.8.0 - with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + - name: Compress kdf build output + run: | + NAME="kdf_$COMMIT_HASH-mac-dylib-x86-64.zip" + mv target/x86_64-apple-darwin/release/libkdflib.a target/x86_64-apple-darwin/release/libkdf.a + zip $NAME target/x86_64-apple-darwin/release/libkdf.a -j + mv $NAME ./$BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" wasm: timeout-minutes: 60 @@ -253,8 +329,8 @@ jobs: - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 rustup target add wasm32-unknown-unknown - name: Install wasm-pack @@ -279,20 +355,19 @@ jobs: - name: Compress build output run: | - NAME="mm2_$COMMIT_HASH-wasm.zip" + NAME="kdf_$COMMIT_HASH-wasm.zip" (cd ./target/target-wasm-release && zip -r - .) > $NAME mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - - name: Upload output - uses: garygrossgarten/github-action-scp@v0.8.0 - with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" ios-aarch64: timeout-minutes: 60 @@ -301,14 +376,14 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 rustup target add aarch64-apple-ios - name: Install build deps uses: ./.github/actions/deps-install with: - deps: ('protoc') + deps: ('protoc', 'python3', 'paramiko') - name: Calculate commit hash for PR commit if: github.event_name == 'pull_request' @@ -327,23 +402,29 @@ jobs: echo $COMMIT_HASH > ./MM_VERSION cargo rustc --target aarch64-apple-ios --lib --release --package mm2_bin_lib --crate-type=staticlib - - name: Compress build output + - name: Compress mm2 build output run: | NAME="mm2_$COMMIT_HASH-ios-aarch64.zip" - mv target/aarch64-apple-ios/release/libmm2lib.a target/aarch64-apple-ios/release/libmm2.a + mv target/aarch64-apple-ios/release/libkdflib.a target/aarch64-apple-ios/release/libmm2.a zip $NAME target/aarch64-apple-ios/release/libmm2.a -j mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - - name: Upload output - uses: garygrossgarten/github-action-scp@v0.8.0 - with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + - name: Compress kdf build output + run: | + NAME="kdf_$COMMIT_HASH-ios-aarch64.zip" + mv target/aarch64-apple-ios/release/libkdflib.a target/aarch64-apple-ios/release/libkdf.a + zip $NAME target/aarch64-apple-ios/release/libkdf.a -j + mv $NAME ./$BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" android-aarch64: timeout-minutes: 60 @@ -361,8 +442,8 @@ jobs: - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 rustup target add aarch64-linux-android - name: Install build deps @@ -392,23 +473,29 @@ jobs: export PATH=$PATH:/android-ndk/bin CC_aarch64_linux_android=aarch64-linux-android21-clang CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android21-clang cargo rustc --target=aarch64-linux-android --lib --release --crate-type=staticlib --package mm2_bin_lib - - name: Compress build output + - name: Compress mm2 build output run: | NAME="mm2_$COMMIT_HASH-android-aarch64.zip" - mv target/aarch64-linux-android/release/libmm2lib.a target/aarch64-linux-android/release/libmm2.a - zip $NAME target/aarch64-linux-android/release/libmm2.a -j + mv target/aarch64-linux-android/release/libkdflib.a target/aarch64-linux-android/release/libmm2.a + zip $NAME target/aarch64-linux-android/release/libmm2.a -j mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - - name: Upload output - uses: garygrossgarten/github-action-scp@v0.8.0 - with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + - name: Compress kdf build output + run: | + NAME="kdf_$COMMIT_HASH-android-aarch64.zip" + mv target/aarch64-linux-android/release/libkdflib.a target/aarch64-linux-android/release/libkdf.a + zip $NAME target/aarch64-linux-android/release/libkdf.a -j + mv $NAME ./$BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" android-armv7: timeout-minutes: 60 @@ -426,8 +513,8 @@ jobs: - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 rustup target add armv7-linux-androideabi - name: Install build deps @@ -457,20 +544,26 @@ jobs: export PATH=$PATH:/android-ndk/bin CC_armv7_linux_androideabi=armv7a-linux-androideabi21-clang CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi21-clang cargo rustc --target=armv7-linux-androideabi --lib --release --crate-type=staticlib --package mm2_bin_lib - - name: Compress build output + - name: Compress mm2 build output run: | NAME="mm2_$COMMIT_HASH-android-armv7.zip" - mv target/armv7-linux-androideabi/release/libmm2lib.a target/armv7-linux-androideabi/release/libmm2.a - zip $NAME target/armv7-linux-androideabi/release/libmm2.a -j + mv target/armv7-linux-androideabi/release/libkdflib.a target/armv7-linux-androideabi/release/libmm2.a + zip $NAME target/armv7-linux-androideabi/release/libmm2.a -j mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - - name: Upload output - uses: garygrossgarten/github-action-scp@v0.8.0 - with: - host: ${{ secrets.FILE_SERVER_HOST }} - username: ${{ secrets.FILE_SERVER_USERNAME }} - port: ${{ secrets.FILE_SERVER_PORT }} - privateKey: ${{ secrets.FILE_SERVER_KEY }} - local: ${{ env.BRANCH_NAME }} - remote: "/uploads/${{ env.BRANCH_NAME }}" + - name: Compress kdf build output + run: | + NAME="kdf_$COMMIT_HASH-android-armv7.zip" + mv target/armv7-linux-androideabi/release/libkdflib.a target/armv7-linux-androideabi/release/libkdf.a + zip $NAME target/armv7-linux-androideabi/release/libkdf.a -j + mv $NAME ./$BRANCH_NAME/ + + - name: Upload build artifact + env: + FILE_SERVER_HOST: ${{ secrets.FILE_SERVER_HOST }} + FILE_SERVER_USERNAME: ${{ secrets.FILE_SERVER_USERNAME }} + FILE_SERVER_PORT: ${{ secrets.FILE_SERVER_PORT }} + FILE_SERVER_KEY: ${{ secrets.FILE_SERVER_KEY }} + if: ${{ env.FILE_SERVER_KEY != '' }} + run: python3 ./scripts/ci/upload_artifact.py "${{ env.BRANCH_NAME }}" "/uploads/${{ env.BRANCH_NAME }}" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4303766a60..12a60bbc3c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,8 +25,8 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 - name: Install build deps uses: ./.github/actions/deps-install @@ -53,8 +53,8 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 - name: Install build deps uses: ./.github/actions/deps-install @@ -81,8 +81,8 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 - name: Install build deps uses: ./.github/actions/deps-install @@ -96,7 +96,7 @@ jobs: run: | cargo test --bins --lib --no-fail-fast - linux-x86-64-mm2-integration: + linux-x86-64-kdf-integration: timeout-minutes: 90 runs-on: ubuntu-latest env: @@ -109,8 +109,8 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 - name: Install build deps uses: ./.github/actions/deps-install @@ -122,10 +122,10 @@ jobs: - name: Test run: | - wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash + wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/0adeeabdd484ef40539d1275c6a765f5c530ea79/zcutil/fetch-params-alt.sh | bash cargo test --test 'mm2_tests_main' --no-fail-fast - mac-x86-64-mm2-integration: + mac-x86-64-kdf-integration: timeout-minutes: 90 runs-on: macos-latest env: @@ -138,8 +138,8 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 - name: Install build deps uses: ./.github/actions/deps-install @@ -154,10 +154,10 @@ jobs: - name: Test run: | - wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash + wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/0adeeabdd484ef40539d1275c6a765f5c530ea79/zcutil/fetch-params-alt.sh | bash cargo test --test 'mm2_tests_main' --no-fail-fast - win-x86-64-mm2-integration: + win-x86-64-kdf-integration: timeout-minutes: 90 runs-on: windows-latest env: @@ -170,8 +170,8 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 - name: Install build deps uses: ./.github/actions/deps-install @@ -181,10 +181,16 @@ jobs: - name: Cargo cache uses: ./.github/actions/cargo-cache + - name: Download wget64 + uses: ./.github/actions/download-and-verify + with: + url: "https://github.com/KomodoPlatform/komodo/raw/d456be35acd1f8584e1e4f971aea27bd0644d5c5/zcutil/wget64.exe" + output_file: "/wget64.exe" + checksum: "d80719431dc22b0e4a070f61fab982b113a4ed9a6d4cf25e64b5be390dcadb94" + - name: Test run: | - Invoke-WebRequest -Uri https://github.com/KomodoPlatform/komodo/raw/d456be35acd1f8584e1e4f971aea27bd0644d5c5/zcutil/wget64.exe -OutFile \wget64.exe - Invoke-WebRequest -Uri https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.bat -OutFile \cmd.bat && \cmd.bat + Invoke-WebRequest -Uri https://raw.githubusercontent.com/KomodoPlatform/komodo/0adeeabdd484ef40539d1275c6a765f5c530ea79/zcutil/fetch-params-alt.bat -OutFile \cmd.bat && \cmd.bat cargo test --test 'mm2_tests_main' --no-fail-fast docker-tests: @@ -200,8 +206,8 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 - name: Install build deps uses: ./.github/actions/deps-install @@ -213,7 +219,7 @@ jobs: - name: Test run: | - wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash + wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/v0.8.1//zcutil/fetch-params-alt.sh | bash cargo test --test 'docker_tests_main' --features run-docker-tests --no-fail-fast wasm: @@ -229,8 +235,8 @@ jobs: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 + rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal + rustup default nightly-2023-06-01 rustup target add wasm32-unknown-unknown - name: Install build deps @@ -241,11 +247,17 @@ jobs: - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + - name: Download geckodriver + uses: ./.github/actions/download-and-verify + with: + url: "https://github.com/mozilla/geckodriver/releases/download/v0.32.2/geckodriver-v0.32.2-linux64.tar.gz" + output_file: "geckodriver-v0.32.2-linux64.tar.gz" + checksum: "1eab226bf009599f5aa1d77d9ed4c374e10a03fd848b500be1b32cefd2cbec64" + - name: Install firefox and geckodriver run: | sudo apt-get update -y sudo apt-get install -y firefox - wget https://github.com/mozilla/geckodriver/releases/download/v0.32.2/geckodriver-v0.32.2-linux64.tar.gz sudo tar -xzvf geckodriver-v0.32.2-linux64.tar.gz -C /bin sudo chmod +x /bin/geckodriver diff --git a/.github/workflows/validate-mm2-version.yml b/.github/workflows/validate-mm2-version.yml index fd8c588c81..4abda09e96 100644 --- a/.github/workflows/validate-mm2-version.yml +++ b/.github/workflows/validate-mm2-version.yml @@ -1,4 +1,4 @@ -name: Validate mm2 version +name: Validate kdf version on: pull_request: diff --git a/.gitignore b/.gitignore index 6eda35f707..42f63e5115 100755 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,9 @@ scripts/mm2/seed/unparsed.txt /js/mm2 /js/mm2.exe /js/.mm2.* +/js/kdf +/js/kdf.exe +/js/.kdf.* # Rust artefacts /MM_DATETIME @@ -70,4 +73,10 @@ MM2.json [._]sw[a-p] # mergetool -*.orig \ No newline at end of file +*.orig + +# Ignore containers runtime directories for dockerized tests +# This directory contains temporary data used by Docker containers during tests execution. +# It is recreated from container-state data each time test containers are started, +# and should not be tracked in version control. +.docker/container-runtime/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e61f3274cc..f3d8816316 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,186 @@ +## v2.2.0-beta - 2024-11-22 + +**Features:** +- Connection Healthcheck + - Connection healthcheck implementation for peers was introduced. [#2194](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2194) +- Custom Tokens Activation + - Support for enabling custom EVM (ERC20, PLG20, etc..) tokens without requiring them to be in the coins config was added. [#2141](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2141) + - This allows users to interact with any ERC20 token by providing the contract address. + +**Enhancements/Fixes:** +- Trading Protocol Upgrade [#1895](https://github.com/KomodoPlatform/atomicDEX-API/issues/1895) + - EVM TPU taker methods were implemented and enhancements were made to ETH docker tests. [#2169](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2169) + - EVM TPU maker methods were implemented. [#2211](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2211) +- NFT integration [#900](https://github.com/KomodoPlatform/atomicDEX-API/issues/900) + - Refund methods for NFT swaps were completed. [#2129](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2129) + - `token_id` field was added to the tx history primary key. [#2209](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2209) +- Graceful Shutdown + - CTRL-C signal handling with graceful shutdown was implemented. [#2213](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2213) +- Seed Management [#1939](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1939) + - A new `get_wallet_names` RPC was added to retrieve information about all wallet names and the currently active one. [#2202](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2202) +- Cosmos Integration [#1432](https://github.com/KomodoPlatform/atomicDEX-API/issues/1432) + - Cosmos tx broadcasting error was fixed by upgrading cosmrs to version 15. [#2238](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2238) + - Cosmos transaction history implementation was incorrectly parsing addresses (using the relayer address instead of the cross-chain address) from IBC transactions. The address parsing logic was fixed in [#2245](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2245) +- Order Management + - Cancel order race condition was addressed using time-based cache. [#2232](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2232) +- Swap Improvements + - A legacy swap issue was resolved where taker spent maker payment transactions were sometimes incorrectly marked as successful when they were actually reverted or not confirmed, particularly in EVM-based swaps. [#2199](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2199) + - Two new events were added: "MakerPaymentSpendConfirmed" and "MakerPaymentSpendConfirmFailed" + - A fix was introduced where Takers don't need to confirm their own payment as they can wait for the spending of it straight away. [#2249](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2249) + - This invalidates this fix [#1442](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1442), a better solution will be introduced where taker rebroadcasts their transaction if it's not on the chain. + - A fix was introduced for recover funds for takers when the swap was marked as unsuccessful due to the maker payment spend transaction not being confirmed. [#2242](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2242) + - The required confirmations from coin config for taker/maker payment spend are now used instead of using 1 confirmation max. This is because some chains require more than 1 confirmation for finality, e.g. Polygon. +- Swap watchers [#1431](https://github.com/KomodoPlatform/atomicDEX-API/issues/1431) + - Taker fee validation retries now work the same way as for makers. [#2263](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2263) +- Electrum Client + - Electrum client was refactored to add min/max connection controls, with server priority based on list order. [#1966](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1966) + - Electrum client can now operate in single-server mode (1,1) to reduce resource usage (especially beneficial for mobile) or multi-server (legacy) mode for reliability. + - Higher priority servers automatically replace lower priority ones when reconnecting during periodic retries or when connection count drops below minimum. +- Coins Activation + - EVM addresses are now displayed in full in iguana v2 activation response. [#2254](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2254) +- HD Wallet [#1838](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1838) + - Balance is now returned as `CoinBalanceMap` for both UTXOs and QTUM. [#2259](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2259) + - This is to return the same type/json across all coins for GUIs since EVM uses `CoinBalanceMap`. + - EVM addresses are displayed in full in `get_new_address` response after [#2264](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2264) +- RPC Service + - A fix was introduced to run rpc request futures till completion in [#1966](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1966) + - This ensures RPC request futures complete fully even if clients disconnect, preventing partial state updates and maintaining data consistency. +- Security Enhancements + - Message lifetime overflows were added to prevent creating messages for proxy with too long lifetimes. [#2233](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2233) + - Remote files are now handled in a safer way in CI. [#2217](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2217) +- Build Process + - `wasm-opt` overriding was removed. [#2200](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2200) +- Escaped response body in native RPC was removed. [#2219](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2219) +- Creation of the all-zeroes dir on KDF start was stopped. [#2218](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2218) +- OPTIONS requests to KDF server were added. [#2191](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2191) + +**Removals:** +- Solana Support [#1085](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1085) + - Solana implementation was removed until it can be redone using the latest Solana SDK. [#2239](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2239) +- Adex-CLI [#1682](https://github.com/KomodoPlatform/atomicDEX-API/issues/1682) + - adex-cli was deprecated pending work on a simpler, more maintainable implementation. [#2234](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2234) + +**Other Changes:** +- Documentation + - Issue link in README was updated. [#2227](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2227) + - Commit badges were updated to use dev branch in README. [#2193](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2193) + - Leftover subcommands were removed from help message. [#2235](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2235) [#2270](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2270) +- Code Structure + - lib.rs was replaced by mm2.rs as the root lib for mm2_main. [#2178](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2178) +- Code Improvements + - P2P feature was added to mm2_net dependency to allow the coins crate to be compiled and tested independently. [#2210](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2210) + - Coins mod clippy warnings in WASM were fixed. [#2224](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2224) + - Nonsense CLI arguments were removed. [#2216](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2216) +- Tests + - Tendermint IBC tests were fixed by preparing IBC channels inside the container. [#2246](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2246) + - `.wait()` usage was replaced with `block_on` in tests to ensure consistent runtime usage, fixing issues with tokio TCP streams in non-tokio runtimes. [#2220](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2220) + - Debug assertions for tests were enabled. [#2204](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2204) + - More Sepolia test endpoints were added in [#2262](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2262) + +**NB - Backwards compatibility breaking changes:** +- RPC Renaming + - `get_peers_info` RPC was renamed to `get_directly_connected_peers`. [#2195](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2195) +- Cosmos Integration [#1432](https://github.com/KomodoPlatform/atomicDEX-API/issues/1432) + - Updates to Tendermint activation payloads: + - 'rpc_urls' field (previously a list of plain string values) is replaced with 'nodes' (a list of JSON objects). [#2173](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2173) +- Komodo DeFi Proxy + - All RPC methods fields controlling komodo-defi-proxy are renamed to 'komodo_proxy', affecting various activations, including ETH/EVM. [#2173](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2173) + + +## v2.1.0-beta - 2024-07-31 + +**Features:** +- Seed Management [#1939](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1939) + - Seed generation, encryption, and storage features were introduced, including a new `get_mnemonic` API. [#2014](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2014) +- A new `sign_raw_transaction` rpc was added for UTXO and EVM coins, this will facilitate air-gapped wallet implementation in the future. [#1930](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1930) + +**Enhancements/Fixes:** +- Event Streaming [#1901](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1901) + - Balance event streaming for Electrum clients was implemented. [#2013](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2013) + - Balance events for UTXOs were introduced. + - Electrum notification receiving bug was fixed. + - Balance event streaming for EVM was added. [#2041](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2041) + - Error events were introduced. [#2041](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2041) + - Heartbeats were introduced to notify about streaming channel health. [#2058](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2058) + - Balance event streaming for ARRR/Pirate was added. [#2076](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2076) +- Trading Protocol Upgrade [#1895](https://github.com/KomodoPlatform/atomicDEX-API/issues/1895) + - *Important note:* Seednodes update is needed to support and rebroadcast new swap protocol messages. [#2015](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2015) + - WASM storage for upgraded swaps introduced. [#2015](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2015) + - Migration of old swaps data was added. [#2015](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2015) + - Swaps now automatically kickstart on MM2 reload. [#2015](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2015) + - File lock for swaps added, preventing the same swap from starting in different processes. [#2015](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2015) + - `my_swap_status`, `my_recent_swaps` V2 RPCs introduced. [#2015](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2015) + - Upgraded swaps data now accessible through V1 RPCs. [#2015](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2015) + - Locked amount handling for UTXO swaps implemented. [#2046](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2046) + - Conditional wait for maker payment confirmation was added before signing funding tx spend preimage on taker's side. [#2046](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2046) + - `active_swaps` V2 RPC introduced. [#2046](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2046) + - Handling `accept_only_from` for swap messages (validation of the sender) was implemented. [#2046](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2046) + - `swap_uuid` for swap v2 messages was added to avoid reuse of the messages generated for other swaps. [#2046](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2046) + - Maker payment immediate refund path handling was implemented. [#2046](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2046) +- KMD Burn [#2010](https://github.com/KomodoPlatform/komodo-defi-framework/issues/2010) + - KMD dex fee burn for upgraded swaps was added. [#2046](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2046) +- Hardware Wallet [#964](https://github.com/KomodoPlatform/atomicDEX-API/issues/964) + - Trezor now supports SegWit for withdrawals. [#1984](https://github.com/KomodoPlatform/atomicDEX-API/pull/1984) + - Trezor support was added for EVM coins/tokens using task manager activation methods. [#1962](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1962) + - Support for unsigned Tendermint transactions using Ledger's Keplr extension was added, excluding HTLC transactions and swap operations. [#2148](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2148) +- NFT integration [#900](https://github.com/KomodoPlatform/atomicDEX-API/issues/900) + - A new `clear_nft_db` RPC for NFT data management was added. This enables selective (based on a chain) or complete NFT DB data clearance. [#2039](https://github.com/KomodoPlatform/atomicDEX-API/pull/2039) + - NFT can now be enabled using `enable_eth_with_tokens` or `enable_nft`, similar to `enable_erc20`. [#2049](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2049) + - NFT swaps V2 POC was shown, which includes a NFT maker payment test using the dockerized Geth dev node. [#2084](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2084) + - `komodo-defi-proxy` support for NFT feature was added. [#2100](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2100) + - Additional checks were added for malicious `token_uri` links. [#2100](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2100) + - `clear_all` parameter in `clear_nft_db` RPC is now optional (default: `false`). [#2100](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2100) +- WASM Worker + - Improved environment detection to ensure the correct method is used for accessing the IndexedDB factory, accommodating both window and worker contexts. [#1953](https://github.com/KomodoPlatform/atomicDEX-API/pull/1953), [#2131](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2131) + - SharedWorker support was added, allowing any worker path in `event_stream_configuration` with a default to `event_streaming_worker.js`. [#2080](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2080) +- Simple Maker Bot [#1065](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1065) + - Maker bot was updated to support multiple price URLs. [#2027](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2027) + - `testcoin` was added to provider options to allow testing the maker bot using test chains assets. [#2161](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2161) +- IndexedDB + - Cursor implementation was fixed, ensuring stable iteration over items. [#2028](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2028) + - Advanced cursor filtering features were added, including limit, offset, and a fix for `where_` condition/option. [#2066](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2066) +- Swap Stats DB + - `stats_swaps` table now includes GUI and MM2 version data used for a swap. [#2061](https://github.com/KomodoPlatform/atomicDEX-API/pull/2061) +- P2P Layer + - Added `max_concurrent_connections` to MM2 config to control the maximum number of concurrent connections for Gossipsub. [#2063](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2063) +- ARRR/Pirate [#927](https://github.com/KomodoPlatform/komodo-defi-framework/issues/927) + - ARRR/Pirate wallet and Dex operations now work in browser environments / WASM. [#1957](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1957), [#2077](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2077) + - Syncing and activation improvements were made, including stopping sync status after main sync and refining `first_sync_block` handling. [#2089](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2089) +- EVM Transport + - ETH websocket transport was introduced. `komodo-defi-proxy` signed messages were also supported for websocket transport. [#2058](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2058) +- Tendermint integration [#1432](https://github.com/KomodoPlatform/atomicDEX-API/issues/1432) + - Nucleus chain support was introduced as an alternative HTLC backend to Iris. [#2079](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2079) + - Tendermint fee calculation was fixed to use `get_receiver_trade_fee` in platform coin. [#2106](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2106) + - Pubkey-only mode for Tendermint protocol was introduced, allowing use of any external wallet for wallet and swap operations. [#2088](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2088) + - `ibc_withdraw` RPC was removed, and `withdraw` was refactored to support IBC transfers by automatically finding IBC channels. [#2088](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2088) + - Transaction history handling was enhanced to support base64 encoded transaction values for Cosmos-based networks, preventing missing transactions in the history table. [#2133](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2133) + - The precision of max amount handling was improved for Tendermint withdraw operations by simulating the transaction and removing the estimated fee. [#2155](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2155) + - Account sequence numbers are now resolved locally, incorrect sequence numbers from cached responses are also avoided. [#2164](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2164) +- HD Wallet [#1838](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1838) + - Full UTXO and EVM HD wallet functionalities were implemented. [#1962](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1962) +- Swap watchers [#1431](https://github.com/KomodoPlatform/atomicDEX-API/issues/1431) + - UTXO swaps were fixed to apply events that occurred while the taker was offline, such as maker spending or watcher refunding the taker payment. [#2114](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2114) +- Fees Improvements [#1848](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1848) + - EIP-1559 gas fee estimator and RPCs were added for ETH, including priority fee support for withdrawals and swaps, and improved gas limit for swap transactions. [#2051](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2051) + - `gas_limit` parameter can be used in coins config to override default gas limit values. [#2137](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2137) + - Default `gas_limit` values now ensure that Proxied ERC20 tokens have sufficient gas. [#2137](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2137) +- Rust Toolchain [#1972](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1972) + - Toolchain was upgraded to Rust toolchain version 1.72 nightly (nightly-2023-06-01). [#2149](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2149) + - rust-analyzer was added into the workspace toolchain. [#2179](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2179) +- CI Builds + - MacOS builds for Apple Silicon are now provided through the CI pipeline. [#2163](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2163) +- Miscellaneous + - BCH block header deserialization was fixed to match BTC's handling of `KAWPOW` version headers. [#2099](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2099) + - Implemented root application directory `.kdf` under `$HOME` to consolidate all runtime files, enhancing user experience by following standard UNIX practices. [#2102](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2102) + - Memory usage was improved a bit through preallocation optimizations. [#2098](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2098) + - Swaps and orders file handling was enhanced to use `.tmp` files to avoid concurrent reading/writing issues. [#2118](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2118) + - UTXO P2PK balance is now shown as part of the P2PKH/Legacy address balance and can be spent in withdrawals and swaps. [#2053](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2053) + - `wallet-only` restriction was removed from `max_maker_vol` RPC, enabling its use for wallet-only mode assets. [#2153](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2153) + +**NB - Backwards compatibility breaking changes:** +- Renamed `mm2` binaries to `kdf`, while providing backward-compatible copies with `mm2` naming; WASM binaries use `kdf` naming only, which is a breaking change. [#2126](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2126) + + ## v2.0.0-beta - 2023-12-15 **Features:** - KMD Burn [#2010](https://github.com/KomodoPlatform/komodo-defi-framework/issues/2010) diff --git a/Cargo.lock b/Cargo.lock index 2adeb0a727..7332358679 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,12 +119,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "aliasable" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -134,15 +128,6 @@ dependencies = [ "libc", ] -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi", -] - [[package]] name = "anyhow" version = "1.0.42" @@ -186,12 +171,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" -[[package]] -name = "assert_matches" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" - [[package]] name = "async-io" version = "1.13.0" @@ -261,7 +240,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -278,7 +257,7 @@ version = "0.1.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -391,12 +370,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base32" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" - [[package]] name = "base58" version = "0.2.0" @@ -409,12 +382,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.0" @@ -471,15 +438,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bip32" version = "0.2.2" @@ -610,20 +568,6 @@ dependencies = [ "constant_time_eq", ] -[[package]] -name = "blake3" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" -dependencies = [ - "arrayref", - "arrayvec 0.7.1", - "cc", - "cfg-if 1.0.0", - "constant_time_eq", - "digest 0.10.7", -] - [[package]] name = "block-buffer" version = "0.9.0" @@ -695,51 +639,6 @@ dependencies = [ "serde_with", ] -[[package]] -name = "borsh" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" -dependencies = [ - "borsh-derive", - "hashbrown 0.11.2", -] - -[[package]] -name = "borsh-derive" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" -dependencies = [ - "borsh-derive-internal", - "borsh-schema-derive-internal", - "proc-macro-crate 0.1.5", - "proc-macro2 1.0.69", - "syn 1.0.95", -] - -[[package]] -name = "borsh-derive-internal" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", -] - -[[package]] -name = "borsh-schema-derive-internal" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", -] - [[package]] name = "bs58" version = "0.4.0" @@ -764,42 +663,12 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" -[[package]] -name = "bv" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" -dependencies = [ - "feature-probe", - "serde", -] - [[package]] name = "byte-slice-cast" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65c1bf4a04a88c54f589125563643d773f3254b5c38571395e2b591c693bbc81" -[[package]] -name = "bytemuck" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e851ca7c24871e7336801608a4797d7376545b6928a10d32d75685687141ead" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", -] - [[package]] name = "byteorder" version = "1.4.3" @@ -825,44 +694,12 @@ dependencies = [ "serde", ] -[[package]] -name = "bzip2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" -dependencies = [ - "bzip2-sys", - "libc", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "cache-padded" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24508e28c677875c380c20f4d28124fab6f8ed4ef929a1397d7b1a31e92f1005" -[[package]] -name = "caps" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61bf7211aad104ce2769ec05efcdfabf85ee84ac92461d142f22cf8badd0e54c" -dependencies = [ - "errno 0.2.8", - "libc", - "thiserror", -] - [[package]] name = "cbc" version = "0.1.2" @@ -877,9 +714,6 @@ name = "cc" version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" -dependencies = [ - "jobserver", -] [[package]] name = "cfg-if" @@ -965,21 +799,6 @@ dependencies = [ "inout", ] -[[package]] -name = "clap" -version = "2.33.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" -dependencies = [ - "ansi_term", - "atty", - "bitflags", - "strsim 0.8.0", - "textwrap", - "unicode-width", - "vec_map", -] - [[package]] name = "cloudabi" version = "0.0.3" @@ -989,15 +808,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "cloudabi" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" -dependencies = [ - "bitflags", -] - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1016,7 +826,6 @@ dependencies = [ "async-trait", "base58", "base64 0.21.7", - "bincode", "bip32", "bitcoin", "bitcoin_hashes", @@ -1026,6 +835,7 @@ dependencies = [ "bytes 0.4.12", "cfg-if 1.0.0", "chain", + "chrono", "common", "cosmrs", "crossbeam 0.8.2", @@ -1034,7 +844,6 @@ dependencies = [ "derive_more", "dirs", "ed25519-dalek", - "ed25519-dalek-bip32 0.2.0", "enum_derives", "ethabi", "ethcore-transaction", @@ -1073,16 +882,19 @@ dependencies = [ "mm2_metrics", "mm2_net", "mm2_number", + "mm2_p2p", "mm2_rpc", "mm2_state_machine", "mm2_test_helpers", "mocktopus", + "nom", "num-traits", - "parking_lot 0.12.0", + "parking_lot", "primitives", "prost", "prost-build", "protobuf", + "proxy_signature", "rand 0.7.3", "regex", "reqwest", @@ -1100,15 +912,12 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "serde_with", "serialization", "serialization_derive", "sha2 0.10.7", "sha3 0.9.1", - "solana-client", - "solana-sdk", - "solana-transaction-status", - "spl-associated-token-account", - "spl-token", + "sia-rust", "spv_validation", "tendermint-rpc", "time 0.3.20", @@ -1120,7 +929,7 @@ dependencies = [ "tower-service", "url", "utxo_signer", - "uuid 1.2.2", + "uuid", "wagyu-zcash-parameters", "wasm-bindgen", "wasm-bindgen-futures", @@ -1158,7 +967,7 @@ dependencies = [ "mm2_metamask", "mm2_metrics", "mm2_number", - "parking_lot 0.12.0", + "parking_lot", "rpc", "rpc_task", "ser_error", @@ -1202,8 +1011,9 @@ dependencies = [ "libc", "lightning", "log", - "parking_lot 0.12.0", + "parking_lot", "parking_lot_core 0.6.2", + "paste", "primitive-types", "rand 0.7.3", "regex", @@ -1218,7 +1028,7 @@ dependencies = [ "sha2 0.10.7", "shared_ref_counter", "tokio", - "uuid 1.2.2", + "uuid", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -1244,21 +1054,6 @@ dependencies = [ "crossbeam-utils 0.8.16", ] -[[package]] -name = "console" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" -dependencies = [ - "encode_unicode", - "libc", - "once_cell", - "regex", - "terminal_size", - "unicode-width", - "winapi", -] - [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -1269,16 +1064,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "console_log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501a375961cef1a0d44767200e66e4a559283097e91d0730b1d75dfb2f8a1494" -dependencies = [ - "log", - "web-sys", -] - [[package]] name = "const-oid" version = "0.9.6" @@ -1318,9 +1103,9 @@ dependencies = [ [[package]] name = "cosmos-sdk-proto" -version = "0.19.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73c9d2043a9e617b0d602fbc0a0ecd621568edbf3a9774890a6d562389bd8e1c" +checksum = "82e23f6ab56d5f031cde05b8b82a5fefd3a1a223595c79e32317a97189e612bc" dependencies = [ "prost", "prost-types", @@ -1329,18 +1114,18 @@ dependencies = [ [[package]] name = "cosmrs" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af13955d6f356272e6def9ff5e2450a7650df536d8934f47052a20c76513d2f6" +checksum = "5d184abb7b0039cc64f282dfa5b34165e4c5a7410ab46804636d53f4d09aee44" dependencies = [ "cosmos-sdk-proto", "ecdsa", "eyre", - "getrandom 0.2.9", "k256", "rand_core 0.6.4", "serde", "serde_json", + "signature 2.2.0", "subtle-encoding", "tendermint", "thiserror", @@ -1550,7 +1335,7 @@ dependencies = [ "mm2_eth", "mm2_metamask", "num-traits", - "parking_lot 0.12.0", + "parking_lot", "primitives", "rpc", "rpc_task", @@ -1601,16 +1386,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "crypto-mac" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "crypto-mac" version = "0.11.1" @@ -1717,7 +1492,7 @@ dependencies = [ "cc", "codespan-reporting", "lazy_static", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "scratch", "syn 1.0.95", @@ -1735,7 +1510,7 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -1758,9 +1533,9 @@ checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", - "strsim 0.10.0", + "strsim", "syn 1.0.95", ] @@ -1775,17 +1550,6 @@ dependencies = [ "syn 1.0.95", ] -[[package]] -name = "dashmap" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" -dependencies = [ - "cfg-if 1.0.0", - "num_cpus", - "rayon", -] - [[package]] name = "data-encoding" version = "2.4.0" @@ -1824,7 +1588,7 @@ dependencies = [ "rusqlite", "sql-builder", "tokio", - "uuid 1.2.2", + "uuid", ] [[package]] @@ -1837,55 +1601,17 @@ dependencies = [ "zeroize", ] -[[package]] -name = "derivation-path" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193388a8c8c75a490b604ff61775e236541b8975e98e5ca1f6ea97d122b7e2db" -dependencies = [ - "failure", -] - -[[package]] -name = "derivation-path" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", -] - [[package]] name = "derive_more" version = "0.99.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] -[[package]] -name = "dialoguer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61579ada4ec0c6031cfac3f86fdba0d195a7ebeb5e36693bd53cb5999a25beeb" -dependencies = [ - "console", - "lazy_static", - "tempfile", - "zeroize", -] - [[package]] name = "digest" version = "0.9.0" @@ -1907,15 +1633,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "dir-diff" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2860407d7d7e2e004bb2128510ad9e8d669e76fa005ccf567977b5d71b8b4a0b" -dependencies = [ - "walkdir", -] - [[package]] name = "directories" version = "3.0.2" @@ -1936,16 +1653,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if 1.0.0", - "dirs-sys-next", -] - [[package]] name = "dirs-sys" version = "0.3.6" @@ -1957,40 +1664,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users 0.4.0", - "winapi", -] - -[[package]] -name = "dlopen" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e80ad39f814a9abe68583cd50a2d45c8a67561c3361ab8da240587dda80937" -dependencies = [ - "dlopen_derive", - "lazy_static", - "libc", - "winapi", -] - -[[package]] -name = "dlopen_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f236d9e1b1fbd81cea0f9cbdc8dcc7e8ebcd80e6659cd7cb2ad5f6c05946c581" -dependencies = [ - "libc", - "quote 0.6.13", - "syn 0.15.44", -] - [[package]] name = "dtoa" version = "1.0.2" @@ -2017,6 +1690,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ + "serde", "signature 1.4.0", ] @@ -2053,44 +1727,20 @@ dependencies = [ "ed25519 1.5.2", "rand 0.7.3", "serde", + "serde_bytes", "sha2 0.9.9", "zeroize", ] [[package]] -name = "ed25519-dalek-bip32" -version = "0.1.1" +name = "edit-distance" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057f328f31294b5ab432e6c39642f54afd1531677d6d4ba2905932844cc242f3" -dependencies = [ - "derivation-path 0.1.3", - "ed25519-dalek", - "failure", - "hmac 0.9.0", - "sha2 0.9.9", -] +checksum = "bbbaaaf38131deb9ca518a274a45bfdb8771f139517b073b16c2d3d32ae5037b" [[package]] -name = "ed25519-dalek-bip32" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" -dependencies = [ - "derivation-path 0.2.0", - "ed25519-dalek", - "hmac 0.12.1", - "sha2 0.10.7", -] - -[[package]] -name = "edit-distance" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbaaaf38131deb9ca518a274a45bfdb8771f139517b073b16c2d3d32ae5037b" - -[[package]] -name = "either" -version = "1.8.1" +name = "either" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" @@ -2113,12 +1763,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - [[package]] name = "encoding_rs" version = "0.8.28" @@ -2141,7 +1785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ "heck", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -2162,7 +1806,7 @@ name = "enum_derives" version = "0.1.0" dependencies = [ "itertools", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -2318,28 +1962,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "failure" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", - "synstructure", -] - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -2361,12 +1983,6 @@ dependencies = [ "instant", ] -[[package]] -name = "feature-probe" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" - [[package]] name = "ff" version = "0.8.0" @@ -2394,18 +2010,6 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" -[[package]] -name = "filetime" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "redox_syscall 0.2.10", - "winapi", -] - [[package]] name = "findshlibs" version = "0.5.0" @@ -2452,7 +2056,6 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" dependencies = [ - "eyre", "paste", ] @@ -2464,11 +2067,10 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "matches", "percent-encoding", ] @@ -2596,7 +2198,7 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -2680,22 +2282,11 @@ version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "serde", "typenum", "version_check", "zeroize", ] -[[package]] -name = "gethostname" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e692e296bfac1d2533ef168d0b60ff5897b8b70a4009276834014dd8924cc028" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "getrandom" version = "0.1.14" @@ -2921,17 +2512,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" -[[package]] -name = "hidapi" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b1717343691998deb81766bfcd1dce6df0d5d6c37070b5a3de2bb6d39f7822" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "hkd32" version = "0.6.0" @@ -2954,16 +2534,6 @@ dependencies = [ "digest 0.9.0", ] -[[package]] -name = "hmac" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff" -dependencies = [ - "crypto-mac 0.9.1", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.11.0" @@ -3194,6 +2764,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "if-addrs" version = "0.7.0" @@ -3256,7 +2836,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5dacb10c5b3bb92d46ba347505a9041e676bb20ad220101326bffb0c93031ee" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -3267,12 +2847,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" -[[package]] -name = "index_list" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9d968042a4902e08810946fc7cd5851eb75e80301342305af755ca06cb82ce" - [[package]] name = "indexmap" version = "1.9.3" @@ -3293,18 +2867,6 @@ dependencies = [ "hashbrown 0.14.3", ] -[[package]] -name = "indicatif" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d207dc617c7a380ab07ff572a6e52fa202a2a8f355860ac9c38e23f8196be1b" -dependencies = [ - "console", - "lazy_static", - "number_prefix", - "regex", -] - [[package]] name = "inout" version = "0.1.3" @@ -3406,15 +2968,6 @@ dependencies = [ "libc", ] -[[package]] -name = "jobserver" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.64" @@ -3462,9 +3015,7 @@ dependencies = [ "cfg-if 1.0.0", "ecdsa", "elliptic-curve", - "once_cell", "sha2 0.10.7", - "signature 2.2.0", ] [[package]] @@ -3534,16 +3085,6 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" -[[package]] -name = "libloading" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" -dependencies = [ - "cfg-if 1.0.0", - "winapi", -] - [[package]] name = "libm" version = "0.1.4" @@ -3559,7 +3100,7 @@ checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libp2p" version = "0.52.1" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "bytes 1.4.0", "futures 0.3.28", @@ -3591,7 +3132,7 @@ dependencies = [ [[package]] name = "libp2p-allow-block-list" version = "0.2.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "libp2p-core", "libp2p-identity", @@ -3602,7 +3143,7 @@ dependencies = [ [[package]] name = "libp2p-connection-limits" version = "0.2.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "libp2p-core", "libp2p-identity", @@ -3613,7 +3154,7 @@ dependencies = [ [[package]] name = "libp2p-core" version = "0.40.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "either", "fnv", @@ -3626,7 +3167,7 @@ dependencies = [ "multihash", "multistream-select", "once_cell", - "parking_lot 0.12.0", + "parking_lot", "pin-project", "quick-protobuf", "rand 0.8.4", @@ -3640,13 +3181,13 @@ dependencies = [ [[package]] name = "libp2p-dns" version = "0.40.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "futures 0.3.28", "libp2p-core", "libp2p-identity", "log", - "parking_lot 0.12.0", + "parking_lot", "smallvec 1.6.1", "trust-dns-resolver", ] @@ -3654,7 +3195,7 @@ dependencies = [ [[package]] name = "libp2p-floodsub" version = "0.43.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "asynchronous-codec", "cuckoofilter", @@ -3674,7 +3215,7 @@ dependencies = [ [[package]] name = "libp2p-gossipsub" version = "0.45.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "asynchronous-codec", "base64 0.21.7", @@ -3705,7 +3246,7 @@ dependencies = [ [[package]] name = "libp2p-identify" version = "0.43.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "asynchronous-codec", "either", @@ -3732,7 +3273,7 @@ dependencies = [ "asn1_der", "bs58 0.5.0", "ed25519-dalek", - "libsecp256k1 0.7.0", + "libsecp256k1", "log", "multihash", "quick-protobuf", @@ -3745,7 +3286,7 @@ dependencies = [ [[package]] name = "libp2p-mdns" version = "0.44.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "data-encoding", "futures 0.3.28", @@ -3765,7 +3306,7 @@ dependencies = [ [[package]] name = "libp2p-metrics" version = "0.13.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "instant", "libp2p-core", @@ -3781,7 +3322,7 @@ dependencies = [ [[package]] name = "libp2p-noise" version = "0.43.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "bytes 1.4.0", "curve25519-dalek 3.2.0", @@ -3805,7 +3346,7 @@ dependencies = [ [[package]] name = "libp2p-ping" version = "0.43.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "either", "futures 0.3.28", @@ -3822,7 +3363,7 @@ dependencies = [ [[package]] name = "libp2p-request-response" version = "0.25.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "async-trait", "futures 0.3.28", @@ -3839,7 +3380,7 @@ dependencies = [ [[package]] name = "libp2p-swarm" version = "0.43.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "either", "fnv", @@ -3861,11 +3402,11 @@ dependencies = [ [[package]] name = "libp2p-swarm-derive" version = "0.33.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "heck", "proc-macro-warning", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -3873,7 +3414,7 @@ dependencies = [ [[package]] name = "libp2p-tcp" version = "0.40.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "futures 0.3.28", "futures-timer", @@ -3889,7 +3430,7 @@ dependencies = [ [[package]] name = "libp2p-wasm-ext" version = "0.40.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "futures 0.3.28", "js-sys", @@ -3902,7 +3443,7 @@ dependencies = [ [[package]] name = "libp2p-websocket" version = "0.42.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "either", "futures 0.3.28", @@ -3910,7 +3451,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "log", - "parking_lot 0.12.0", + "parking_lot", "quicksink", "rw-stream-sink", "soketto", @@ -3921,7 +3462,7 @@ dependencies = [ [[package]] name = "libp2p-yamux" version = "0.44.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "either", "futures 0.3.28", @@ -3932,25 +3473,6 @@ dependencies = [ "yamux 0.13.1", ] -[[package]] -name = "libsecp256k1" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" -dependencies = [ - "arrayref", - "base64 0.12.3", - "digest 0.9.0", - "hmac-drbg", - "libsecp256k1-core 0.2.2", - "libsecp256k1-gen-ecmult 0.2.1", - "libsecp256k1-gen-genmult 0.2.1", - "rand 0.7.3", - "serde", - "sha2 0.9.9", - "typenum", -] - [[package]] name = "libsecp256k1" version = "0.7.0" @@ -3961,26 +3483,15 @@ dependencies = [ "base64 0.13.0", "digest 0.9.0", "hmac-drbg", - "libsecp256k1-core 0.3.0", - "libsecp256k1-gen-ecmult 0.3.0", - "libsecp256k1-gen-genmult 0.3.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", "rand 0.8.4", "serde", "sha2 0.9.9", "typenum", ] -[[package]] -name = "libsecp256k1-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" -dependencies = [ - "crunchy", - "digest 0.9.0", - "subtle", -] - [[package]] name = "libsecp256k1-core" version = "0.3.0" @@ -3992,31 +3503,13 @@ dependencies = [ "subtle", ] -[[package]] -name = "libsecp256k1-gen-ecmult" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" -dependencies = [ - "libsecp256k1-core 0.2.2", -] - [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" dependencies = [ - "libsecp256k1-core 0.3.0", -] - -[[package]] -name = "libsecp256k1-gen-genmult" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" -dependencies = [ - "libsecp256k1-core 0.2.2", + "libsecp256k1-core", ] [[package]] @@ -4025,7 +3518,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" dependencies = [ - "libsecp256k1-core 0.3.0", + "libsecp256k1-core", ] [[package]] @@ -4222,15 +3715,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "memmap2" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" -dependencies = [ - "libc", -] - [[package]] name = "memoffset" version = "0.5.6" @@ -4295,7 +3779,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddece26afd34c31585c74a4db0630c376df271c285d682d1e55012197830b6df" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -4357,7 +3841,7 @@ dependencies = [ [[package]] name = "mm2_bin_lib" -version = "2.1.0-beta" +version = "2.3.0-beta" dependencies = [ "chrono", "common", @@ -4393,6 +3877,7 @@ dependencies = [ "hex", "instant", "lazy_static", + "libp2p", "mm2_err_handle", "mm2_event_stream", "mm2_metrics", @@ -4406,7 +3891,7 @@ dependencies = [ "serde_json", "shared_ref_counter", "tokio", - "uuid 1.2.2", + "uuid", "wasm-bindgen-test", ] @@ -4476,7 +3961,7 @@ dependencies = [ "cfg-if 1.0.0", "common", "futures 0.3.28", - "parking_lot 0.12.0", + "parking_lot", "serde", "tokio", "wasm-bindgen-test", @@ -4591,7 +4076,8 @@ dependencies = [ "mocktopus", "num-traits", "parity-util-mem", - "parking_lot 0.12.0", + "parking_lot", + "primitive-types", "primitives", "prost", "prost-build", @@ -4615,14 +4101,17 @@ dependencies = [ "serde_json", "serialization", "serialization_derive", + "sia-rust", "sp-runtime-interface", "sp-trie", "spv_validation", "testcontainers", "tokio", + "trading_api", "trie-db", "trie-root 0.16.0", - "uuid 1.2.2", + "url", + "uuid", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -4645,7 +4134,7 @@ dependencies = [ "lazy_static", "mm2_err_handle", "mm2_eth", - "parking_lot 0.12.0", + "parking_lot", "serde", "serde_derive", "serde_json", @@ -4697,10 +4186,8 @@ dependencies = [ "lazy_static", "mm2_core", "mm2_err_handle", - "mm2_event_stream", - "mm2_p2p", + "mm2_number", "mm2_state_machine", - "parking_lot 0.12.0", "pin-project", "prost", "rand 0.7.3", @@ -4749,12 +4236,17 @@ dependencies = [ "lazy_static", "libp2p", "log", + "mm2_core", + "mm2_event_stream", + "mm2_number", + "parking_lot", "rand 0.7.3", "regex", "rmp-serde", "secp256k1 0.20.3", "serde", "serde_bytes", + "serde_json", "sha2 0.10.7", "smallvec 1.6.1", "syn 2.0.38", @@ -4778,7 +4270,7 @@ dependencies = [ "ser_error_derive", "serde", "serde_json", - "uuid 1.2.2", + "uuid", ] [[package]] @@ -4816,7 +4308,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "uuid 1.2.2", + "uuid", ] [[package]] @@ -4834,7 +4326,7 @@ version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3048ef3680533a27f9f8e7d6a0bce44dc61e4895ea0f42709337fa1c8616fefe" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -4888,7 +4380,7 @@ checksum = "d8883adfde9756c1d30b0f519c9b8c502a94b41ac62f696453c37c7fc0a958ce" [[package]] name = "multistream-select" version = "0.13.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "bytes 1.4.0", "futures 0.3.28", @@ -4973,19 +4465,6 @@ dependencies = [ "smallvec 1.6.1", ] -[[package]] -name = "nix" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" -dependencies = [ - "bitflags", - "cc", - "cfg-if 1.0.0", - "libc", - "memoffset 0.6.4", -] - [[package]] name = "nix" version = "0.24.3" @@ -5030,13 +4509,13 @@ dependencies = [ [[package]] name = "num-derive" -version = "0.3.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", - "syn 1.0.95", + "syn 2.0.38", ] [[package]] @@ -5081,34 +4560,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_enum" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" -dependencies = [ - "derivative", - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" -dependencies = [ - "proc-macro-crate 1.1.3", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", -] - -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - [[package]] name = "object" version = "0.29.0" @@ -5145,30 +4596,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "ouroboros" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f357ef82d1b4db66fbed0b8d542cbd3c22d0bf5b393b3c257b9ba4568e70c9c3" -dependencies = [ - "aliasable", - "ouroboros_macro", - "stable_deref_trait", -] - -[[package]] -name = "ouroboros_macro" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44a0b52c2cbaef7dffa5fec1a43274afe8bd2a644fa9fc50a9ef4ff0269b1257" -dependencies = [ - "Inflector", - "proc-macro-error", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", -] - [[package]] name = "packed_simd_2" version = "0.3.8" @@ -5209,8 +4636,8 @@ version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c45ed1f39709f5a89338fab50e59816b2e8815f5bb58276e7ddf9afd495f73f8" dependencies = [ - "proc-macro-crate 1.1.3", - "proc-macro2 1.0.69", + "proc-macro-crate", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -5227,7 +4654,7 @@ dependencies = [ "impl-trait-for-tuples", "lru 0.7.5", "parity-util-mem-derive", - "parking_lot 0.12.0", + "parking_lot", "primitive-types", "smallvec 1.6.1", "winapi", @@ -5239,7 +4666,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "syn 1.0.95", "synstructure", ] @@ -5256,17 +4683,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" -[[package]] -name = "parking_lot" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.0", -] - [[package]] name = "parking_lot" version = "0.12.0" @@ -5284,7 +4700,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" dependencies = [ "cfg-if 0.1.10", - "cloudabi 0.0.3", + "cloudabi", "libc", "redox_syscall 0.1.56", "rustc_version 0.2.3", @@ -5292,21 +4708,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "parking_lot_core" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" -dependencies = [ - "cfg-if 0.1.10", - "cloudabi 0.1.0", - "instant", - "libc", - "redox_syscall 0.1.56", - "smallvec 1.6.1", - "winapi", -] - [[package]] name = "parking_lot_core" version = "0.9.1" @@ -5338,49 +4739,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" [[package]] -name = "pbkdf2" -version = "0.4.0" +name = "peg" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +checksum = "295283b02df346d1ef66052a757869b2876ac29a6bb0ac3f5f7cd44aebe40e8f" dependencies = [ - "crypto-mac 0.8.0", + "peg-macros", + "peg-runtime", ] [[package]] -name = "pbkdf2" -version = "0.9.0" +name = "peg-macros" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" +checksum = "bdad6a1d9cf116a059582ce415d5f5566aabcd4008646779dab7fdc2a9a9d426" dependencies = [ - "crypto-mac 0.11.1", -] - -[[package]] -name = "peg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" -dependencies = [ - "peg-macros", - "peg-runtime", -] - -[[package]] -name = "peg-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" -dependencies = [ - "peg-runtime", - "proc-macro2 1.0.69", - "quote 1.0.33", + "peg-runtime", + "proc-macro2", + "quote 1.0.33", ] [[package]] name = "peg-runtime" -version = "0.7.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" +checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a" [[package]] name = "pem" @@ -5393,9 +4776,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "petgraph" @@ -5422,7 +4805,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -5520,12 +4903,12 @@ checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" [[package]] name = "prettyplease" -version = "0.1.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28f53e8b192565862cf99343194579a022eb9c7dd3a8d03134734803c7b3125" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ - "proc-macro2 1.0.69", - "syn 1.0.95", + "proc-macro2", + "syn 2.0.38", ] [[package]] @@ -5552,15 +4935,6 @@ dependencies = [ "uint", ] -[[package]] -name = "proc-macro-crate" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" -dependencies = [ - "toml", -] - [[package]] name = "proc-macro-crate" version = "1.1.3" @@ -5568,31 +4942,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ "thiserror", - "toml", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "version_check", + "toml 0.5.7", ] [[package]] @@ -5601,20 +4951,11 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70550716265d1ec349c41f70dd4f964b4fd88394efe4405f0c1da679c4799a07" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] -[[package]] -name = "proc-macro2" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -dependencies = [ - "unicode-xid 0.1.0", -] - [[package]] name = "proc-macro2" version = "1.0.69" @@ -5632,7 +4973,7 @@ checksum = "78c2f43e8969d51935d2a7284878ae053ba30034cd563f673cde37ba5205685e" dependencies = [ "dtoa", "itoa 1.0.10", - "parking_lot 0.12.0", + "parking_lot", "prometheus-client-derive-encode", ] @@ -5642,16 +4983,16 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b6a5217beb0ad503ee7fa752d451c905113d70721b937126158f3106a48cc1" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] [[package]] name = "prost" -version = "0.11.9" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes 1.4.0", "prost-derive", @@ -5659,44 +5000,43 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.11.9" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes 1.4.0", "heck", "itertools", - "lazy_static", "log", "multimap", + "once_cell", "petgraph", "prettyplease", "prost", "prost-types", "regex", - "syn 1.0.95", + "syn 2.0.38", "tempfile", - "which", ] [[package]] name = "prost-derive" -version = "0.11.9" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", - "syn 1.0.95", + "syn 2.0.38", ] [[package]] name = "prost-types" -version = "0.11.9" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ "prost", ] @@ -5727,12 +5067,15 @@ dependencies = [ ] [[package]] -name = "qstring" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +name = "proxy_signature" +version = "0.1.0" dependencies = [ - "percent-encoding", + "chrono", + "http 0.2.12", + "libp2p", + "rand 0.7.3", + "serde", + "serde_json", ] [[package]] @@ -5769,7 +5112,7 @@ dependencies = [ [[package]] name = "quick-protobuf-codec" version = "0.2.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "asynchronous-codec", "bytes 1.4.0", @@ -5795,22 +5138,13 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" -[[package]] -name = "quote" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" -dependencies = [ - "proc-macro2 0.4.30", -] - [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", ] [[package]] @@ -6002,7 +5336,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" dependencies = [ - "cloudabi 0.0.3", + "cloudabi", "fuchsia-cprng", "libc", "rand_core 0.4.2", @@ -6048,31 +5382,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "rayon" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" -dependencies = [ - "autocfg 1.1.0", - "crossbeam-deque 0.8.1", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" -dependencies = [ - "crossbeam-channel 0.5.1", - "crossbeam-deque 0.8.1", - "crossbeam-utils 0.8.16", - "lazy_static", - "num_cpus", -] - [[package]] name = "rcgen" version = "0.10.0" @@ -6145,7 +5454,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c523ccaed8ac4b0288948849a350b37d3035827413c458b6a40ddb614bb4f72" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -6306,16 +5615,6 @@ dependencies = [ "serde", ] -[[package]] -name = "rpassword" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "rpc" version = "0.1.0" @@ -6358,7 +5657,7 @@ dependencies = [ "log", "netlink-packet-route", "netlink-proto", - "nix 0.24.3", + "nix", "thiserror", "tokio", ] @@ -6553,7 +5852,7 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "rw-stream-sink" version = "0.4.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc061b58853c1b0dafaa19a4a29343c0ac6eab3" dependencies = [ "futures 0.3.28", "pin-project", @@ -6593,8 +5892,8 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50e334bb10a245e28e5fd755cabcafd96cfcd167c99ae63a46924ca8d8703a3c" dependencies = [ - "proc-macro-crate 1.1.3", - "proc-macro2 1.0.69", + "proc-macro-crate", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -6778,7 +6077,7 @@ dependencies = [ name = "ser_error_derive" version = "0.1.0" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "ser_error", "syn 1.0.95", @@ -6819,7 +6118,7 @@ version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -6842,11 +6141,20 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -6876,23 +6184,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ "darling", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] -[[package]] -name = "serde_yaml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0" -dependencies = [ - "indexmap 1.9.3", - "ryu", - "serde", - "yaml-rust", -] - [[package]] name = "serialization" version = "0.1.0" @@ -6989,6 +6285,26 @@ dependencies = [ "log", ] +[[package]] +name = "sia-rust" +version = "0.1.0" +source = "git+https://github.com/KomodoPlatform/sia-rust?rev=9f188b80b3213bcb604e7619275251ce08fae808#9f188b80b3213bcb604e7619275251ce08fae808" +dependencies = [ + "base64 0.21.7", + "blake2b_simd", + "chrono", + "derive_more", + "ed25519-dalek", + "hex", + "nom", + "reqwest", + "rustc-hex", + "serde", + "serde_json", + "serde_with", + "url", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -7084,667 +6400,54 @@ dependencies = [ "rand_core 0.6.4", "ring 0.16.20", "rustc_version 0.4.0", - "sha2 0.10.7", - "subtle", -] - -[[package]] -name = "socket2" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "soketto" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "083624472e8817d44d02c0e55df043737ff11f279af924abdf93845717c2b75c" -dependencies = [ - "base64 0.13.0", - "bytes 1.4.0", - "futures 0.3.28", - "httparse", - "log", - "rand 0.8.4", - "sha-1", -] - -[[package]] -name = "solana-account-decoder" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea8c1862fc46c6ab40d83d15ced24a7afb1f3422da5824f1e9260f5ac10141f" -dependencies = [ - "Inflector", - "base64 0.12.3", - "bincode", - "bs58 0.4.0", - "bv", - "lazy_static", - "serde", - "serde_derive", - "serde_json", - "solana-config-program", - "solana-sdk", - "solana-vote-program", - "spl-token", - "thiserror", - "zstd", -] - -[[package]] -name = "solana-address-lookup-table-program" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c60728aec35d772e6614319558cdccbe3f845102699b65ba5ac7497da0b626a" -dependencies = [ - "bincode", - "bytemuck", - "log", - "num-derive", - "num-traits", - "rustc_version 0.4.0", - "serde", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-program-runtime", - "solana-sdk", - "thiserror", -] - -[[package]] -name = "solana-bloom" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddcd7c6adb802bc812a5a80c8de06ba0f0e8df0cca296a8b4e67cd04c16218f" -dependencies = [ - "bv", - "fnv", - "log", - "rand 0.7.3", - "rayon", - "rustc_version 0.4.0", - "serde", - "serde_derive", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-sdk", -] - -[[package]] -name = "solana-bucket-map" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3435b145894971a58a08a7b6be997ec782239fdecd5edd9846cd1d6aa5986" -dependencies = [ - "fs_extra", - "log", - "memmap2", - "rand 0.7.3", - "rayon", - "solana-logger", - "solana-measure", - "solana-sdk", - "tempfile", -] - -[[package]] -name = "solana-clap-utils" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8417a89c377728dbfbf1966b6493544f6e5e168ebc5bb444f3526481fae94e31" -dependencies = [ - "chrono", - "clap", - "rpassword", - "solana-perf", - "solana-remote-wallet", - "solana-sdk", - "thiserror", - "tiny-bip39", - "uriparse", - "url", -] - -[[package]] -name = "solana-cli-config" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e8b011d36369ef2bc3dff63fee078bf2916a4fd21f3aa702ee731c7ddf83d28" -dependencies = [ - "dirs-next", - "lazy_static", - "serde", - "serde_derive", - "serde_yaml", - "url", -] - -[[package]] -name = "solana-client" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e20f4df8cee4a1819f1c5b0d3d85d50c30f27133b2ae68c2fd92655e4aede34a" -dependencies = [ - "base64 0.13.0", - "bincode", - "bs58 0.4.0", - "clap", - "indicatif", - "jsonrpc-core", - "log", - "rayon", - "reqwest", - "semver 1.0.6", - "serde", - "serde_derive", - "serde_json", - "solana-account-decoder", - "solana-clap-utils", - "solana-faucet", - "solana-measure", - "solana-net-utils", - "solana-sdk", - "solana-transaction-status", - "solana-version", - "solana-vote-program", - "thiserror", - "tokio", - "tungstenite", - "url", -] - -[[package]] -name = "solana-compute-budget-program" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685567c221f6bb5b64387f7b45d03036ad112b2ecbcd0f94b11204efab9f891e" -dependencies = [ - "solana-program-runtime", - "solana-sdk", -] - -[[package]] -name = "solana-config-program" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f4b04403ff77f09eba5cf94078c1178161e26d346245b06180866ab5286fe6b" -dependencies = [ - "bincode", - "chrono", - "serde", - "serde_derive", - "solana-program-runtime", - "solana-sdk", -] - -[[package]] -name = "solana-faucet" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a11e1b6d5ce435bb3df95f2a970cd80500a8abf94ea87558c35fe0cce8456ab" -dependencies = [ - "bincode", - "byteorder", - "clap", - "log", - "serde", - "serde_derive", - "solana-clap-utils", - "solana-cli-config", - "solana-logger", - "solana-metrics", - "solana-sdk", - "solana-version", - "spl-memo", - "thiserror", - "tokio", -] - -[[package]] -name = "solana-frozen-abi" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5f69a79200f5ba439eb8b790c5e00beab4d1fae4da69ce023c69c6ac1b55bf1" -dependencies = [ - "bs58 0.4.0", - "bv", - "generic-array", - "log", - "memmap2", - "rustc_version 0.4.0", - "serde", - "serde_derive", - "sha2 0.9.9", - "solana-frozen-abi-macro", - "solana-logger", - "thiserror", -] - -[[package]] -name = "solana-frozen-abi-macro" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402fffb54bf5d335e6df26fc1719feecfbd7a22fafdf6649fe78380de3c47384" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "rustc_version 0.4.0", - "syn 1.0.95", -] - -[[package]] -name = "solana-logger" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942dc59fc9da66d178362051b658646b65dc11cea0bc804e4ecd2528d3c1279f" -dependencies = [ - "env_logger", - "lazy_static", - "log", -] - -[[package]] -name = "solana-measure" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ccd5b1278b115249d6ca5a203fd852f7d856e048488c24442222ee86e682bd9" -dependencies = [ - "log", - "solana-sdk", -] - -[[package]] -name = "solana-metrics" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9774cd8309f599797b1612731fbc56df6c612879ab4923a3dc7234400eea419" -dependencies = [ - "env_logger", - "gethostname", - "lazy_static", - "log", - "reqwest", - "solana-sdk", -] - -[[package]] -name = "solana-net-utils" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb530af085d8aab563530ed39703096aa526249d350082823882fdd59cdf839" -dependencies = [ - "bincode", - "clap", - "log", - "nix 0.23.1", - "rand 0.7.3", - "serde", - "serde_derive", - "socket2 0.4.9", - "solana-logger", - "solana-sdk", - "solana-version", - "tokio", - "url", -] - -[[package]] -name = "solana-perf" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4117c0cf7753bc18f3a09f4973175c3f2c7c5d8e3c9bc15cab09060b06f3434f" -dependencies = [ - "ahash 0.7.6", - "bincode", - "bv", - "caps", - "curve25519-dalek 3.2.0", - "dlopen", - "dlopen_derive", - "fnv", - "lazy_static", - "libc", - "log", - "nix 0.23.1", - "rand 0.7.3", - "rayon", - "serde", - "solana-bloom", - "solana-logger", - "solana-metrics", - "solana-rayon-threadlimit", - "solana-sdk", - "solana-vote-program", -] - -[[package]] -name = "solana-program" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a463f546a2f5842d35974bd4691ae5ceded6785ec24db440f773723f6ce4e11" -dependencies = [ - "base64 0.13.0", - "bincode", - "bitflags", - "blake3", - "borsh", - "borsh-derive", - "bs58 0.4.0", - "bv", - "bytemuck", - "console_error_panic_hook", - "console_log", - "curve25519-dalek 3.2.0", - "getrandom 0.1.14", - "itertools", - "js-sys", - "lazy_static", - "libsecp256k1 0.6.0", - "log", - "num-derive", - "num-traits", - "parking_lot 0.11.1", - "rand 0.7.3", - "rustc_version 0.4.0", - "rustversion", - "serde", - "serde_bytes", - "serde_derive", - "sha2 0.9.9", - "sha3 0.9.1", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-logger", - "solana-sdk-macro", - "thiserror", - "wasm-bindgen", -] - -[[package]] -name = "solana-program-runtime" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09841673334eab958d5bedab9c9d75ed2ff7a7ef70e7dfd6b239c6838a3d79ec" -dependencies = [ - "base64 0.13.0", - "bincode", - "itertools", - "libc", - "libloading", - "log", - "num-derive", - "num-traits", - "rustc_version 0.4.0", - "serde", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-logger", - "solana-measure", - "solana-sdk", - "thiserror", -] - -[[package]] -name = "solana-rayon-threadlimit" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92893e3129dfabb703cd045e1367f3ced91202a2d0b6179a3dcd62ad6bead3b" -dependencies = [ - "lazy_static", - "num_cpus", -] - -[[package]] -name = "solana-remote-wallet" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "315534baecaae3f804548ccc4738d73ae01bf6523219787ebe55ee66d8db9a85" -dependencies = [ - "base32", - "console", - "dialoguer", - "hidapi", - "log", - "num-derive", - "num-traits", - "parking_lot 0.11.1", - "qstring", - "semver 1.0.6", - "solana-sdk", - "thiserror", - "uriparse", -] - -[[package]] -name = "solana-runtime" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd06e905260433f7e8d18bccb2e2eb2aa5cc53379d104d331ddeb12e13230a0" -dependencies = [ - "arrayref", - "bincode", - "blake3", - "bv", - "bytemuck", - "byteorder", - "bzip2", - "crossbeam-channel 0.5.1", - "dashmap", - "dir-diff", - "flate2", - "fnv", - "index_list", - "itertools", - "lazy_static", - "log", - "memmap2", - "num-derive", - "num-traits", - "num_cpus", - "ouroboros", - "rand 0.7.3", - "rayon", - "regex", - "rustc_version 0.4.0", - "serde", - "serde_derive", - "solana-address-lookup-table-program", - "solana-bloom", - "solana-bucket-map", - "solana-compute-budget-program", - "solana-config-program", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-logger", - "solana-measure", - "solana-metrics", - "solana-program-runtime", - "solana-rayon-threadlimit", - "solana-sdk", - "solana-stake-program", - "solana-vote-program", - "symlink", - "tar", - "tempfile", - "thiserror", - "zstd", -] - -[[package]] -name = "solana-sdk" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6560e605c68fa1e3e66a9d3c8529d097d402e1183f80dd06a2c870d0ecb795c2" -dependencies = [ - "assert_matches", - "base64 0.13.0", - "bincode", - "bitflags", - "borsh", - "bs58 0.4.0", - "bytemuck", - "byteorder", - "chrono", - "derivation-path 0.1.3", - "digest 0.9.0", - "ed25519-dalek", - "ed25519-dalek-bip32 0.1.1", - "generic-array", - "hmac 0.11.0", - "itertools", - "js-sys", - "lazy_static", - "libsecp256k1 0.6.0", - "log", - "memmap2", - "num-derive", - "num-traits", - "pbkdf2 0.9.0", - "qstring", - "rand 0.7.3", - "rand_chacha 0.2.2", - "rustc_version 0.4.0", - "rustversion", - "serde", - "serde_bytes", - "serde_derive", - "serde_json", - "sha2 0.9.9", - "sha3 0.9.1", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-logger", - "solana-program", - "solana-sdk-macro", - "thiserror", - "uriparse", - "wasm-bindgen", -] - -[[package]] -name = "solana-sdk-macro" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c834b4e02ac911b13c13aed08b3f847e722f6be79d31b1c660c1dbd2dee83cdb" -dependencies = [ - "bs58 0.4.0", - "proc-macro2 1.0.69", - "quote 1.0.33", - "rustversion", - "syn 1.0.95", + "sha2 0.10.7", + "subtle", ] [[package]] -name = "solana-stake-program" -version = "1.9.20" +name = "socket2" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92597c0ed16d167d5ee48e5b13e92dfaed9c55b23a13ec261440136cd418649" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ - "bincode", - "log", - "num-derive", - "num-traits", - "rustc_version 0.4.0", - "serde", - "serde_derive", - "solana-config-program", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-metrics", - "solana-program-runtime", - "solana-sdk", - "solana-vote-program", - "thiserror", + "cfg-if 1.0.0", + "libc", + "winapi", ] [[package]] -name = "solana-transaction-status" -version = "1.9.20" +name = "socket2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612a51efa19380992e81fc64a2fb55d42aed32c67d795848d980cbe1f9693250" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ - "Inflector", - "base64 0.12.3", - "bincode", - "bs58 0.4.0", - "lazy_static", - "log", - "serde", - "serde_derive", - "serde_json", - "solana-account-decoder", - "solana-measure", - "solana-metrics", - "solana-runtime", - "solana-sdk", - "solana-vote-program", - "spl-associated-token-account", - "spl-memo", - "spl-token", - "thiserror", + "libc", + "winapi", ] [[package]] -name = "solana-version" -version = "1.9.20" +name = "socket2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222e2c91640d45cd9617dfc07121555a9bdac10e6e105f6931b758f46db6faaa" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" dependencies = [ - "log", - "rustc_version 0.4.0", - "serde", - "serde_derive", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-sdk", + "libc", + "windows-sys 0.48.0", ] [[package]] -name = "solana-vote-program" -version = "1.9.20" +name = "soketto" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4cc64945010e9e76d368493ad091aa5cf43ee16f69296290ebb5c815e433232" +checksum = "083624472e8817d44d02c0e55df043737ff11f279af924abdf93845717c2b75c" dependencies = [ - "bincode", + "base64 0.13.0", + "bytes 1.4.0", + "futures 0.3.28", + "httparse", "log", - "num-derive", - "num-traits", - "rustc_version 0.4.0", - "serde", - "serde_derive", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-logger", - "solana-metrics", - "solana-program-runtime", - "solana-sdk", - "thiserror", + "rand 0.8.4", + "sha-1", ] [[package]] @@ -7778,7 +6481,7 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d676664972e22a0796176e81e7bec41df461d1edf52090955cdab55f2c956ff2" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -7807,8 +6510,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ecb916b9664ed9f90abef0ff5a3e61454c1efea5861b2997e03f39b59b955f" dependencies = [ "Inflector", - "proc-macro-crate 1.1.3", - "proc-macro2 1.0.69", + "proc-macro-crate", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -7892,39 +6595,6 @@ dependencies = [ "der", ] -[[package]] -name = "spl-associated-token-account" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "393e2240d521c3dd770806bff25c2c00d761ac962be106e14e22dd912007f428" -dependencies = [ - "solana-program", - "spl-token", -] - -[[package]] -name = "spl-memo" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325" -dependencies = [ - "solana-program", -] - -[[package]] -name = "spl-token" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93bfdd5bd7c869cb565c7d7635c4fafe189b988a0bdef81063cd9585c6b8dc01" -dependencies = [ - "arrayref", - "num-derive", - "num-traits", - "num_enum", - "solana-program", - "thiserror", -] - [[package]] name = "spv_validation" version = "0.1.0" @@ -7962,31 +6632,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f9799e6d412271cb2414597581128b03f3285f260ea49f5363d07df6a332b3e" dependencies = [ "Inflector", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "serde", "serde_json", "unicode-xid 0.2.0", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "strsim" version = "0.10.0" @@ -8014,12 +6672,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" -[[package]] -name = "symlink" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" - [[package]] name = "syn" version = "0.11.11" @@ -8031,24 +6683,13 @@ dependencies = [ "unicode-xid 0.0.4", ] -[[package]] -name = "syn" -version = "0.15.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "unicode-xid 0.1.0", -] - [[package]] name = "syn" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "unicode-ident", ] @@ -8059,7 +6700,7 @@ version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "unicode-ident", ] @@ -8085,7 +6726,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", "unicode-xid 0.2.0", @@ -8118,17 +6759,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "tar" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" -dependencies = [ - "filetime", - "libc", - "xattr", -] - [[package]] name = "tempfile" version = "3.4.0" @@ -8144,9 +6774,9 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.32.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f0a7d05cf78524782337f8edd55cbc578d159a16ad4affe2135c92f7dbac7f0" +checksum = "43f8a10105d0a7c4af0a242e23ed5a12519afe5cc0e68419da441bb5981a6802" dependencies = [ "bytes 1.4.0", "digest 0.10.7", @@ -8175,23 +6805,23 @@ dependencies = [ [[package]] name = "tendermint-config" -version = "0.32.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71a72dbbea6dde12045d261f2c70c0de039125675e8a026c8d5ad34522756372" +checksum = "ac6bf36c613bb113737c333e3c1d6dfd3c99f8ac679e84feb58dd6456d77fb2e" dependencies = [ "flex-error", "serde", "serde_json", "tendermint", - "toml", + "toml 0.8.19", "url", ] [[package]] name = "tendermint-proto" -version = "0.32.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cec054567d16d85e8c3f6a3139963d1a66d9d3051ed545d31562550e9bcc3d" +checksum = "ff525d5540a9fc535c38dc0d92a98da3ee36fcdfbda99cecb9f3cce5cd4d41d7" dependencies = [ "bytes 1.4.0", "flex-error", @@ -8207,9 +6837,9 @@ dependencies = [ [[package]] name = "tendermint-rpc" -version = "0.32.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d119d83a130537fc4a98c3c9eb6899ebe857fea4860400a61675bfb5f0b35129" +checksum = "2d8fe61b1772cd50038bdeeadf53773bb37a09e639dd8e6d996668fd220ddb29" dependencies = [ "async-trait", "bytes 1.4.0", @@ -8217,6 +6847,7 @@ dependencies = [ "getrandom 0.2.9", "peg", "pin-project", + "rand 0.8.4", "semver 1.0.6", "serde", "serde_bytes", @@ -8229,7 +6860,7 @@ dependencies = [ "thiserror", "time 0.3.20", "url", - "uuid 0.8.2", + "uuid", "walkdir", ] @@ -8242,16 +6873,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "test_helpers" version = "0.1.0" @@ -8276,15 +6897,6 @@ dependencies = [ "sha2 0.10.7", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - [[package]] name = "thiserror" version = "1.0.40" @@ -8300,7 +6912,7 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -8343,25 +6955,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-bip39" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" -dependencies = [ - "anyhow", - "hmac 0.8.1", - "once_cell", - "pbkdf2 0.4.0", - "rand 0.7.3", - "rustc-hash", - "sha2 0.9.9", - "thiserror", - "unicode-normalization", - "wasm-bindgen", - "zeroize", -] - [[package]] name = "tiny-keccak" version = "1.4.4" @@ -8406,7 +6999,6 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot 0.12.0", "pin-project-lite 0.2.9", "signal-hook-registry", "socket2 0.4.9", @@ -8440,7 +7032,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -8533,11 +7125,45 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap 2.2.3", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" -version = "0.9.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" dependencies = [ "async-stream", "async-trait", @@ -8545,8 +7171,6 @@ dependencies = [ "base64 0.21.7", "bytes 1.4.0", "flate2", - "futures-core", - "futures-util", "h2", "http 0.2.12", "http-body 0.4.5", @@ -8555,6 +7179,7 @@ dependencies = [ "percent-encoding", "pin-project", "prost", + "rustls 0.21.10", "rustls-pemfile 1.0.2", "tokio", "tokio-rustls 0.24.1", @@ -8563,20 +7188,20 @@ dependencies = [ "tower-layer", "tower-service", "tracing", - "webpki-roots 0.23.1", + "webpki-roots 0.25.4", ] [[package]] name = "tonic-build" -version = "0.9.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" +checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" dependencies = [ "prettyplease", - "proc-macro2 1.0.69", + "proc-macro2", "prost-build", "quote 1.0.33", - "syn 1.0.95", + "syn 2.0.38", ] [[package]] @@ -8629,7 +7254,7 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -8643,6 +7268,26 @@ dependencies = [ "once_cell", ] +[[package]] +name = "trading_api" +version = "0.1.0" +dependencies = [ + "common", + "derive_more", + "enum_derives", + "ethereum-types", + "lazy_static", + "mm2_core", + "mm2_err_handle", + "mm2_net", + "mm2_number", + "mocktopus", + "serde", + "serde_derive", + "serde_json", + "url", +] + [[package]] name = "trezor" version = "0.1.1" @@ -8715,7 +7360,7 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna", + "idna 0.2.3", "ipnet", "lazy_static", "rand 0.8.4", @@ -8739,7 +7384,7 @@ dependencies = [ "ipconfig", "lazy_static", "lru-cache", - "parking_lot 0.12.0", + "parking_lot", "resolv-conf", "smallvec 1.6.1", "thiserror", @@ -8773,7 +7418,6 @@ dependencies = [ "url", "utf-8", "webpki", - "webpki-roots 0.22.3", ] [[package]] @@ -8801,12 +7445,9 @@ source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git?rev=mm2- [[package]] name = "unicode-bidi" -version = "0.3.4" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -dependencies = [ - "matches", -] +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -8816,9 +7457,9 @@ checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -8835,12 +7476,6 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" -[[package]] -name = "unicode-xid" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" - [[package]] name = "unicode-xid" version = "0.2.0" @@ -8879,25 +7514,14 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "uriparse" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e515b1ada404168e145ac55afba3c42f04cf972201a8552d42e2abb17c1b7221" -dependencies = [ - "fnv", - "lazy_static", -] - [[package]] name = "url" -version = "2.2.2" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna", - "matches", + "idna 0.5.0", "percent-encoding", "serde", ] @@ -8928,15 +7552,9 @@ dependencies = [ [[package]] name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - -[[package]] -name = "uuid" -version = "1.2.2" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom 0.2.9", "rand 0.8.4", @@ -8955,12 +7573,6 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.4" @@ -9081,7 +7693,7 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", "wasm-bindgen-shared", @@ -9115,7 +7727,7 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", "wasm-bindgen-backend", @@ -9148,7 +7760,7 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c2e18093f11c19ca4e188c177fecc7c372304c311189f12c2f9bea5b7324ac7" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", ] @@ -9178,11 +7790,11 @@ dependencies = [ "getrandom 0.2.9", "headers", "hex", - "idna", + "idna 0.2.3", "js-sys", "jsonrpc-core", "log", - "parking_lot 0.12.0", + "parking_lot", "pin-project", "rand 0.8.4", "reqwest", @@ -9239,16 +7851,6 @@ dependencies = [ "cc", ] -[[package]] -name = "which" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe" -dependencies = [ - "either", - "libc", -] - [[package]] name = "widestring" version = "0.5.1" @@ -9519,6 +8121,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.7.0" @@ -9554,24 +8165,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "xattr" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" -dependencies = [ - "libc", -] - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "yamux" version = "0.12.1" @@ -9581,7 +8174,7 @@ dependencies = [ "futures 0.3.28", "log", "nohash-hasher", - "parking_lot 0.12.0", + "parking_lot", "pin-project", "rand 0.8.4", "static_assertions", @@ -9597,7 +8190,7 @@ dependencies = [ "instant", "log", "nohash-hasher", - "parking_lot 0.12.0", + "parking_lot", "pin-project", "rand 0.8.4", "static_assertions", @@ -9758,37 +8351,8 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", "synstructure", ] - -[[package]] -name = "zstd" -version = "0.9.2+zstd.1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "4.1.3+zstd.1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "1.6.2+zstd.1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" -dependencies = [ - "cc", - "libc", -] diff --git a/Cargo.toml b/Cargo.toml index b87feeb988..507c2e5c31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,15 +36,14 @@ members = [ "mm2src/mm2_p2p", "mm2src/mm2_rpc", "mm2src/mm2_state_machine", + "mm2src/proxy_signature", "mm2src/rpc_task", "mm2src/trezor", + "mm2src/trading_api", ] exclude = [ "mm2src/adex_cli", - "mm2src/floodsub", - "mm2src/gossipsub", - "mm2src/mm2_libp2p", "mm2src/mm2_test_helpers", ] @@ -58,11 +57,7 @@ opt-level = 3 strip = true codegen-units = 1 # lto = true -panic = "abort" - -[profile.test] -# required to avoid a long running process of librustcash additional chain validation that is enabled with debug assertions -debug-assertions = false +panic = 'unwind' [profile.dev] opt-level = 0 @@ -70,4 +65,7 @@ debug = 1 debug-assertions = false panic = 'unwind' incremental = true -codegen-units = 256 \ No newline at end of file +codegen-units = 256 + +[profile.release.package.mocktopus] +opt-level = 1 # TODO: MIR fails on optimizing this dependency, remove that.. diff --git a/README.md b/README.md index c301a29cba..441c23ba8c 100755 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ downloads - last commit + last commit - +
@@ -48,7 +48,7 @@ ## What is the Komodo DeFi Framework? -The Komodo DeFi Framework is open-source [atomic-swap](https://komodoplatform.com/en/academy/atomic-swaps/) software for seamless, decentralised, peer to peer trading between almost every blockchain asset in existence. This software works with propagation of orderbooks and swap states through the [libp2p](https://libp2p.io/) protocol and uses [Hash Time Lock Contracts (HTLCs)](https://en.bitcoinwiki.org/wiki/Hashed_Timelock_Contracts) for ensuring that the two parties in a swap either mutually complete a trade, or funds return to thier original owner. +The Komodo DeFi Framework is open-source [atomic-swap](https://komodoplatform.com/en/academy/atomic-swaps/) software for seamless, decentralized, peer to peer trading between almost every blockchain asset in existence. This software works with propagation of orderbooks and swap states through the [libp2p](https://libp2p.io/) protocol and uses [Hash Time Lock Contracts (HTLCs)](https://en.bitcoinwiki.org/wiki/Hashed_Timelock_Contracts) for ensuring that the two parties in a swap either mutually complete a trade, or funds return to thier original owner. There is no 3rd party intermediary, no proxy tokens, and at all times users remain in sole possession of their private keys. @@ -89,13 +89,13 @@ If you want to build from source without installing prerequisites to your host s Build the image: ```sh -docker build -t mm2-build-container -f .docker/Dockerfile . +docker build -t kdf-build-container -f .docker/Dockerfile . ``` Bind source code into container and compile it: ```sh -docker run -v "$(pwd)":/app -w /app mm2-build-container cargo build +docker run -v "$(pwd)":/app -w /app kdf-build-container cargo build ``` Just like building it on your host system, you will now have the target directory containing the build files. @@ -122,11 +122,11 @@ For example: The coins file contains information about the coins and tokens you want to trade. A regularly updated version is maintained in the [Komodo Platform coins repository](https://github.com/KomodoPlatform/coins/blob/master/coins). Pull Requests to add any coins not yet included are welcome. -To facilitate interoperability with the `mm2` service, there is the `adex-cli` command line utility. It provides a questionnaire initialization mode to set up the configuration and obtain the proper coin set through the internet. It can also be used to start or stop the service. +To facilitate interoperability with the `kdf` service, there is the `adex-cli` command line utility. It provides a questionnaire initialization mode to set up the configuration and obtain the proper coin set through the internet. It can also be used to start or stop the service. ## Usage -To launch the Komodo DeFi Framework, run `./mm2` (or `mm2.exe` in Windows) +To launch the Komodo DeFi Framework, run `./kdf` (or `kdf.exe` in Windows) To activate a coin: ```bash @@ -172,7 +172,7 @@ Refer to the [Komodo Developer Docs](https://developers.komodoplatform.com/basic ## Project structure -[mm2src](mm2src) - Rust code, contains some parts ported from C `as is` (e.g. `lp_ordermatch`) to reach the most essential/error prone code. Some other modules/crates are reimplemented from scratch. +[mm2src](mm2src) - Rust code, contains some parts ported from C `as is` (e.g. `lp_ordermatch`) to reach the most essential/error-prone code. Some other modules/crates are reimplemented from scratch. ## Additional docs for developers @@ -185,12 +185,13 @@ Refer to the [Komodo Developer Docs](https://developers.komodoplatform.com/basic ## Disclaimer -This repository contains the `work in progress` code of the brand new Komodo DeFi Framework (mm2) built mainly on Rust. -The current state can be considered as a alpha version. +This repository contains the `work in progress` code of the brand-new Komodo DeFi Framework (kdf) built mainly on Rust. +The current state can be considered as an alpha version. **WARNING: Use with test coins only or with assets which value does not exceed an amount you are willing to lose. This is alpha stage software! ** ## Help and troubleshooting -If you have any question/want to report a bug/suggest an improvement feel free to [open an issue](https://github.com/artemii235/SuperNET/issues/new) or join the [Komodo Platform Discord](https://discord.gg/PGxVm2y) `dev-marketmaker` channel. +If you have any question/want to report a bug/suggest an improvement feel free to [open an issue](https://github.com/KomodoPlatform/komodo-defi-framework/issues/new/choose) or join the [Komodo Platform Discord](https://discord.gg/PGxVm2y) `dev-marketmaker` channel. + diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000000..068d7e886b --- /dev/null +++ b/clippy.toml @@ -0,0 +1,4 @@ +[[disallowed-methods]] +path = "futures::future::Future::wait" +replacement = "common::block_on_f01" +reason = "Use the default KDF async executor." \ No newline at end of file diff --git a/docs/ANDROID_CROSS_ON_M1_MAC.md b/docs/ANDROID_CROSS_ON_M1_MAC.md index 666e7be92c..1233c05588 100644 --- a/docs/ANDROID_CROSS_ON_M1_MAC.md +++ b/docs/ANDROID_CROSS_ON_M1_MAC.md @@ -1,4 +1,4 @@ -## Cross-compiling MM for Android on M1 Mac +## Cross-compiling MM for Android on Apple Silicon 1. Ensure that your terminal is added to `Developer tools` in MacOS Security & Privacy settings. 2. The cross-compilation requires Android NDK version 21. Custom brew cask file is located at the root of this repo. diff --git a/docs/DEV_ENVIRONMENT.md b/docs/DEV_ENVIRONMENT.md index f6185b7249..5e4f6d1659 100644 --- a/docs/DEV_ENVIRONMENT.md +++ b/docs/DEV_ENVIRONMENT.md @@ -36,7 +36,7 @@ ``` sudo ln -s $(which podman) /usr/bin/docker ``` -9. Try `cargo test --features "native run-docker-tests" --all -- --test-threads=16`. +9. Try `cargo test --all --features run-docker-tests -- --test-threads=16`. ## Running WASM tests @@ -61,12 +61,12 @@ ``` CC=/usr/local/opt/llvm/bin/clang AR=/usr/local/opt/llvm/bin/llvm-ar wasm-pack test --firefox --headless mm2src/mm2_main ``` - - for OSX users (M1): + - for OSX users (Apple Silicon): ``` CC=/opt/homebrew/opt/llvm/bin/clang AR=/opt/homebrew/opt/llvm/bin/llvm-ar wasm-pack test --firefox --headless mm2src/mm2_main ``` Please note `CC` and `AR` must be specified in the same line as `wasm-pack test mm2src/mm2_main`. -#### Running specific WASM tests with Cargo
+#### Running specific WASM tests with Cargo
- Install `wasm-bindgen-cli`:
Make sure you have wasm-bindgen-cli installed with a version that matches the one specified in your Cargo.toml file. You can install it using Cargo with the following command: diff --git a/docs/WASM_BUILD.md b/docs/WASM_BUILD.md index 13d568dffc..eb62fa7731 100644 --- a/docs/WASM_BUILD.md +++ b/docs/WASM_BUILD.md @@ -25,7 +25,7 @@ To build WASM release binary run one of the following commands according to your ``` CC=/usr/local/opt/llvm/bin/clang AR=/usr/local/opt/llvm/bin/llvm-ar wasm-pack build mm2src/mm2_bin_lib --target web --out-dir wasm_build/deps/pkg/ ``` -- for OSX users (M1): +- for OSX users (Apple Silicon): ``` CC=/opt/homebrew/opt/llvm/bin/clang AR=/opt/homebrew/opt/llvm/bin/llvm-ar wasm-pack build mm2src/mm2_bin_lib --target web --out-dir wasm_build/deps/pkg/ ``` diff --git a/mm2src/adex_cli/Cargo.lock b/mm2src/adex_cli/Cargo.lock index 0693a28246..5d5eb5abeb 100644 --- a/mm2src/adex_cli/Cargo.lock +++ b/mm2src/adex_cli/Cargo.lock @@ -21,7 +21,7 @@ dependencies = [ "common", "derive_more", "directories", - "env_logger 0.7.1", + "env_logger", "gstuff", "http 0.2.9", "hyper", @@ -29,6 +29,7 @@ dependencies = [ "inquire", "itertools", "log", + "mm2_core", "mm2_net", "mm2_number", "mm2_rpc", @@ -158,6 +159,125 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd" +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.1.0", + "futures-lite 2.3.0", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite 1.13.0", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg 1.1.0", + "cfg-if 1.0.0", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling", + "rustix", + "slab", + "socket2", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-process" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" +dependencies = [ + "async-io", + "async-lock", + "autocfg 1.1.0", + "blocking", + "cfg-if 1.0.0", + "event-listener", + "futures-lite 1.13.0", + "rustix", + "signal-hook", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 1.13.0", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.52" @@ -166,9 +286,15 @@ checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "atty" version = "0.2.14" @@ -325,6 +451,22 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "blocking" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c36a4d0d48574b3dd360b4b7d95cc651d2b6557b6402848a27d4b228a473e2a" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "fastrand 2.1.0", + "futures-io", + "futures-lite 1.13.0", + "piper", + "tracing", +] + [[package]] name = "bumpalo" version = "3.12.0" @@ -485,7 +627,7 @@ dependencies = [ "chrono", "crossbeam", "derive_more", - "env_logger 0.9.3", + "env_logger", "findshlibs", "fnv", "futures 0.1.31", @@ -527,6 +669,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -628,13 +779,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" -dependencies = [ - "cfg-if 1.0.0", - "lazy_static", -] +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crossterm" @@ -711,7 +858,7 @@ dependencies = [ "proc-macro2", "quote 1.0.27", "scratch", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -728,7 +875,7 @@ checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -754,7 +901,7 @@ checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -821,19 +968,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime 1.3.0", - "log", - "regex", - "termcolor", -] - [[package]] name = "env_logger" version = "0.9.3" @@ -841,7 +975,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", - "humantime 2.1.0", + "humantime", "log", "regex", "termcolor", @@ -904,7 +1038,7 @@ dependencies = [ [[package]] name = "ethkey" version = "0.3.0" -source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#3326a6c3c12c1655f9dec57ad28b0983d8c08997" +source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git?rev=mm2-v2.1.1#d5524212230c4773d01b2527e9b3c422a251e0dc" dependencies = [ "byteorder", "edit-distance", @@ -919,6 +1053,12 @@ dependencies = [ "tiny-keccak 1.4.4", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -931,6 +1071,21 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + [[package]] name = "findshlibs" version = "0.5.0" @@ -1026,6 +1181,34 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.1.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.28" @@ -1113,6 +1296,18 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "groestl" version = "0.9.0" @@ -1281,15 +1476,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error", -] - [[package]] name = "humantime" version = "2.1.0" @@ -1396,7 +1582,7 @@ checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -1541,6 +1727,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1599,11 +1794,11 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" dependencies = [ - "cfg-if 1.0.0", + "value-bag", ] [[package]] @@ -1624,7 +1819,7 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "mem" version = "0.1.0" -source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#3326a6c3c12c1655f9dec57ad28b0983d8c08997" +source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git?rev=mm2-v2.1.1#d5524212230c4773d01b2527e9b3c422a251e0dc" [[package]] name = "memchr" @@ -1726,6 +1921,7 @@ name = "mm2_core" version = "0.1.0" dependencies = [ "arrayref", + "async-std", "async-trait", "cfg-if 1.0.0", "common", @@ -1734,17 +1930,23 @@ dependencies = [ "futures 0.3.28", "gstuff", "hex", + "instant", "lazy_static", + "mm2_err_handle", "mm2_event_stream", "mm2_metrics", "mm2_rpc", "primitives", "rand 0.7.3", "rustls", + "ser_error", + "ser_error_derive", "serde", "serde_json", "shared_ref_counter", + "tokio", "uuid", + "wasm-bindgen-test", ] [[package]] @@ -2021,9 +2223,15 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.0" @@ -2124,12 +2332,39 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" +dependencies = [ + "atomic-waker", + "fastrand 2.1.0", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg 1.1.0", + "bitflags", + "cfg-if 1.0.0", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + [[package]] name = "portable-atomic" version = "1.3.2" @@ -2210,7 +2445,7 @@ dependencies = [ "itertools", "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -2229,12 +2464,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quote" version = "0.3.15" @@ -2879,7 +3108,7 @@ dependencies = [ "proc-macro2", "quote 1.0.27", "ser_error", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -2933,7 +3162,7 @@ checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -3136,9 +3365,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.95" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote 1.0.27", @@ -3219,7 +3448,7 @@ checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -3320,7 +3549,7 @@ checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -3518,6 +3747,12 @@ dependencies = [ "serde", ] +[[package]] +name = "value-bag" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" + [[package]] name = "vcpkg" version = "0.2.15" @@ -3530,6 +3765,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + [[package]] name = "want" version = "0.3.0" diff --git a/mm2src/adex_cli/Cargo.toml b/mm2src/adex_cli/Cargo.toml index 8deb6c488c..cb477cacb0 100644 --- a/mm2src/adex_cli/Cargo.toml +++ b/mm2src/adex_cli/Cargo.toml @@ -7,23 +7,24 @@ description = "Provides a CLI interface and facilitates interoperating to komodo # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -anyhow = { version = "=1.0.42", features = ["std"] } -async-trait = "=0.1.52" +anyhow = { version = "1.0", features = ["std"] } +async-trait = "0.1" clap = { version = "4.2", features = ["derive"] } common = { path = "../common" } derive_more = "0.99" directories = "5.0" -env_logger = "0.7.1" +env_logger = "0.9.3" http = "0.2" hyper = { version = "0.14.26", features = ["client", "http2", "tcp"] } -hyper-rustls = "0.24.0" -gstuff = { version = "=0.7.4" , features = [ "nightly" ]} +hyper-rustls = "0.24" +gstuff = { version = "0.7" , features = [ "nightly" ]} inquire = "0.6" itertools = "0.10" -log = "0.4" +log = "0.4.21" mm2_net = { path = "../mm2_net" } mm2_number = { path = "../mm2_number" } mm2_rpc = { path = "../mm2_rpc"} +mm2_core = { path = "../mm2_core" } passwords = "3.1" rpc = { path = "../mm2_bitcoin/rpc" } rustls = { version = "0.21", features = [ "dangerous_configuration" ] } @@ -31,8 +32,8 @@ serde = "1.0" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } sysinfo = "0.28" tiny-bip39 = "0.8.0" -tokio = { version = "=1.25.0", features = [ "macros" ] } -uuid = { version = "=1.2.2", features = ["fast-rng", "serde", "v4"] } +tokio = { version = "1.20.0", features = [ "macros" ] } +uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.3", features = ["processthreadsapi", "winnt"] } diff --git a/mm2src/adex_cli/README.md b/mm2src/adex_cli/README.md new file mode 100644 index 0000000000..6066b2c0bc --- /dev/null +++ b/mm2src/adex_cli/README.md @@ -0,0 +1,3 @@ +## ⚠️ Deprecated + +**The `adex-cli` tool is no longer maintained and has been deprecated.** \ No newline at end of file diff --git a/mm2src/adex_cli/src/adex_proc/adex_proc_impl.rs b/mm2src/adex_cli/src/adex_proc/adex_proc_impl.rs index 33d4fbfb62..24c108c6da 100644 --- a/mm2src/adex_cli/src/adex_proc/adex_proc_impl.rs +++ b/mm2src/adex_cli/src/adex_proc/adex_proc_impl.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, bail, Result}; -use log::{error, info, warn}; +use log::{debug, error, info, warn}; use mm2_rpc::data::legacy::{BalanceResponse, CoinInitResponse, GetEnabledResponse, Mm2RpcResult, MmVersionResponse, OrderbookRequest, OrderbookResponse, SellBuyRequest, SellBuyResponse, Status}; use serde_json::{json, Value as Json}; @@ -38,7 +38,7 @@ impl AdexProc<'_, '_, let activation_scheme = get_activation_scheme()?; let activation_method = activation_scheme.get_activation_method(asset)?; - + debug!("Got activation scheme for the coin: {}, {:?}", asset, activation_method); let enable = Command::builder() .flatten_data(activation_method) .userpass(self.get_rpc_password()?) diff --git a/mm2src/adex_cli/src/rpc_data.rs b/mm2src/adex_cli/src/rpc_data.rs index a3146cbe47..2c634759ef 100644 --- a/mm2src/adex_cli/src/rpc_data.rs +++ b/mm2src/adex_cli/src/rpc_data.rs @@ -40,6 +40,10 @@ pub(crate) struct ElectrumRequest { #[serde(skip_serializing_if = "Vec::is_empty")] pub(super) servers: Vec, #[serde(skip_serializing_if = "Option::is_none")] + min_connected: Option, + #[serde(skip_serializing_if = "Option::is_none")] + max_connected: Option, + #[serde(skip_serializing_if = "Option::is_none")] mm2: Option, #[serde(default)] tx_history: bool, @@ -62,4 +66,5 @@ pub(super) struct Server { protocol: ElectrumProtocol, #[serde(default)] disable_cert_verification: bool, + pub timeout_sec: Option, } diff --git a/mm2src/adex_cli/src/scenarios/init_mm2_cfg.rs b/mm2src/adex_cli/src/scenarios/init_mm2_cfg.rs index d58b035f73..1fef03bf30 100644 --- a/mm2src/adex_cli/src/scenarios/init_mm2_cfg.rs +++ b/mm2src/adex_cli/src/scenarios/init_mm2_cfg.rs @@ -107,7 +107,7 @@ impl Mm2Cfg { self.dbdir = CustomType::>::new("What is dbdir") .with_placeholder(DEFAULT_OPTION_PLACEHOLDER) - .with_help_message("AtomicDEX API database path. Optional, defaults to a subfolder named DB in the path of your mm2 binary") + .with_help_message("Komodo DeFi Framework database path. Optional, defaults to a subfolder named DB in the path of your mm2 binary") .with_validator(is_reachable_dir) .prompt() .map_err(|error| @@ -128,7 +128,7 @@ impl Mm2Cfg { fn inquire_net_id(&mut self) -> Result<()> { self.netid = CustomType::::new("What is the network `mm2` is going to be a part, netid:") .with_default(DEFAULT_NET_ID) - .with_help_message(r#"Network ID number, telling the AtomicDEX API which network to join. 8762 is the current main network, though alternative netids can be used for testing or "private" trades"#) + .with_help_message(r#"Network ID number, telling the Komodo DeFi Framework which network to join. 8762 is the current main network, though alternative netids can be used for testing or "private" trades"#) .with_placeholder(format!("{DEFAULT_NET_ID}").as_str()) .prompt() .map_err(|error| @@ -268,7 +268,7 @@ impl Mm2Cfg { .with_formatter(DEFAULT_OPTION_BOOL_FORMATTER) .with_default_value_formatter(DEFAULT_DEFAULT_OPTION_BOOL_FORMATTER) .with_default(InquireOption::None) - .with_help_message("If false the AtomicDEX API will allow rpc methods sent from external IP addresses. Optional, defaults to true. Warning: Only use this if you know what you are doing, and have put the appropriate security measures in place.") + .with_help_message("If false the Komodo DeFi Framework will allow rpc methods sent from external IP addresses. Optional, defaults to true. Warning: Only use this if you know what you are doing, and have put the appropriate security measures in place.") .prompt() .map_err(|error| error_anyhow!("Failed to get rpc_local_only: {error}") @@ -283,7 +283,7 @@ impl Mm2Cfg { .with_formatter(DEFAULT_OPTION_BOOL_FORMATTER) .with_default_value_formatter(DEFAULT_DEFAULT_OPTION_BOOL_FORMATTER) .with_default(InquireOption::None) - .with_help_message("Runs AtomicDEX API as a seed node mode (acting as a relay for AtomicDEX API clients). Optional, defaults to false. Use of this mode is not reccomended on the main network (8762) as it could result in a pubkey ban if non-compliant. on alternative testing or private networks, at least one seed node is required to relay information to other AtomicDEX API clients using the same netID.") + .with_help_message("Runs Komodo DeFi Framework as a seed node mode (acting as a relay for Komodo DeFi Framework clients). Optional, defaults to false. Use of this mode is not reccomended on the main network (8762) as it could result in a pubkey ban if non-compliant. on alternative testing or private networks, at least one seed node is required to relay information to other Komodo DeFi Framework clients using the same netID.") .prompt() .map_err(|error| error_anyhow!("Failed to get i_am_a_seed: {error}") diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index fcc83c7697..290a0dd7f5 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -5,26 +5,16 @@ edition = "2018" [features] zhtlc-native-tests = [] -# TODO -enable-solana = [ - "dep:bincode", - "dep:ed25519-dalek-bip32", - "dep:solana-client", - "dep:solana-sdk", - "dep:solana-transaction-status", - "dep:spl-token", - "dep:spl-associated-token-account" -] enable-sia = [ "dep:reqwest", - "blake2b_simd" + "dep:blake2b_simd", + "dep:sia-rust" ] default = [] run-docker-tests = [] for-tests = [] [lib] -name = "coins" path = "lp_coins.rs" doctest = false @@ -41,13 +31,14 @@ byteorder = "1.3" bytes = "0.4" cfg-if = "1.0" chain = { path = "../mm2_bitcoin/chain" } +chrono = { version = "0.4.23", "features" = ["serde"] } common = { path = "../common" } -cosmrs = { version = "0.14.0", default-features = false } +cosmrs = { version = "0.16", default-features = false } crossbeam = "0.8" crypto = { path = "../crypto" } db_common = { path = "../db_common" } derive_more = "0.99" -ed25519-dalek = "1.0.1" +ed25519-dalek = { version = "1.0.1", features = ["serde"] } enum_derives = { path = "../derives/enum_derives" } ethabi = { version = "17.0.0" } ethcore-transaction = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git", rev = "mm2-v2.1.1" } @@ -69,6 +60,7 @@ jsonrpc-core = "18.0.0" keys = { path = "../mm2_bitcoin/keys" } lazy_static = "1.4" libc = "0.2" +nom = "6.1.2" mm2_core = { path = "../mm2_core" } mm2_err_handle = { path = "../mm2_err_handle" } mm2_event_stream = { path = "../mm2_event_stream" } @@ -77,14 +69,16 @@ mm2_io = { path = "../mm2_io" } mm2_metrics = { path = "../mm2_metrics" } mm2_net = { path = "../mm2_net" } mm2_number = { path = "../mm2_number"} +mm2_p2p = { path = "../mm2_p2p" } mm2_rpc = { path = "../mm2_rpc" } mm2_state_machine = { path = "../mm2_state_machine" } mocktopus = "0.8.0" num-traits = "0.2" parking_lot = { version = "0.12.0", features = ["nightly"] } primitives = { path = "../mm2_bitcoin/primitives" } -prost = "0.11" +prost = "0.12" protobuf = "2.20" +proxy_signature = { path = "../proxy_signature" } rand = { version = "0.7", features = ["std", "small_rng"] } regex = "1" reqwest = { version = "0.11.9", default-features = false, features = ["json"], optional = true } @@ -99,14 +93,16 @@ ser_error_derive = { path = "../derives/ser_error_derive" } serde = "1.0" serde_derive = "1.0" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } +serde_with = "1.14.0" serialization = { path = "../mm2_bitcoin/serialization" } serialization_derive = { path = "../mm2_bitcoin/serialization_derive" } +sia-rust = { git = "https://github.com/KomodoPlatform/sia-rust", rev = "9f188b80b3213bcb604e7619275251ce08fae808", optional = true } spv_validation = { path = "../mm2_bitcoin/spv_validation" } sha2 = "0.10" sha3 = "0.9" utxo_signer = { path = "utxo_signer" } # using the same version as cosmrs -tendermint-rpc = { version = "0.32.0", default-features = false } +tendermint-rpc = { version = "0.35", default-features = false } tokio-tungstenite-wasm = { git = "https://github.com/KomodoPlatform/tokio-tungstenite-wasm", rev = "d20abdb", features = ["rustls-tls-native-roots"]} url = { version = "2.2.2", features = ["serde"] } uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } @@ -118,15 +114,6 @@ zcash_client_backend = { git = "https://github.com/KomodoPlatform/librustzcash.g zcash_extras = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.4.1" } zcash_primitives = {features = ["transparent-inputs"], git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.4.1" } -[target.'cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))'.dependencies] -bincode = { version = "1.3.3", default-features = false, optional = true } -ed25519-dalek-bip32 = { version = "0.2.0", default-features = false, optional = true } -solana-client = { version = "1", default-features = false, optional = true } -solana-sdk = { version = "1", default-features = false, optional = true } -solana-transaction-status = { version = "1", optional = true } -spl-token = { version = "3", optional = true } -spl-associated-token-account = { version = "1", optional = true } - [target.'cfg(target_arch = "wasm32")'.dependencies] blake2b_simd = "0.5" ff = "0.8" @@ -138,7 +125,7 @@ mm2_db = { path = "../mm2_db" } mm2_metamask = { path = "../mm2_metamask" } mm2_test_helpers = { path = "../mm2_test_helpers" } time = { version = "0.3.20", features = ["wasm-bindgen"] } -tonic = { version = "0.9", default-features = false, features = ["prost", "codegen", "gzip"] } +tonic = { version = "0.10", default-features = false, features = ["prost", "codegen", "gzip"] } tower-service = "0.3" wasm-bindgen = "0.2.86" wasm-bindgen-futures = { version = "0.4.1" } @@ -163,7 +150,7 @@ rustls = { version = "0.21", features = ["dangerous_configuration"] } secp256k1v24 = { version = "0.24", package = "secp256k1" } tokio = { version = "1.20" } tokio-rustls = { version = "0.24" } -tonic = { version = "0.9", features = ["tls", "tls-webpki-roots", "gzip"] } +tonic = { version = "0.10", features = ["tls", "tls-webpki-roots", "gzip"] } webpki-roots = { version = "0.25" } zcash_client_sqlite = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.4.1" } zcash_proofs = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.4.1", default-features = false, features = ["local-prover", "multicore"] } @@ -178,5 +165,5 @@ mm2_test_helpers = { path = "../mm2_test_helpers" } wagyu-zcash-parameters = { version = "0.2" } [build-dependencies] -prost-build = { version = "0.11", default-features = false } -tonic-build = { version = "0.9", default-features = false, features = ["prost"] } +prost-build = { version = "0.12", default-features = false } +tonic-build = { version = "0.10", default-features = false, features = ["prost"] } diff --git a/mm2src/coins/coin_balance.rs b/mm2src/coins/coin_balance.rs index 26f838a5fb..f3ba916c33 100644 --- a/mm2src/coins/coin_balance.rs +++ b/mm2src/coins/coin_balance.rs @@ -139,20 +139,18 @@ pub struct HDAddressBalance { #[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] +#[derive(Default)] pub enum EnableCoinScanPolicy { /// Don't scan for new addresses. DoNotScan, /// Scan for new addresses if the coin HD wallet hasn't been enabled *only*. /// In other words, scan for new addresses if there were no HD accounts in the HD wallet storage. + #[default] ScanIfNewWallet, /// Scan for new addresses even if the coin HD wallet has been enabled before. Scan, } -impl Default for EnableCoinScanPolicy { - fn default() -> Self { EnableCoinScanPolicy::ScanIfNewWallet } -} - #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct EnabledCoinBalanceParams { #[serde(default)] @@ -414,8 +412,7 @@ pub enum AddressBalanceStatus { pub mod common_impl { use super::*; use crate::hd_wallet::{create_new_account, ExtractExtendedPubkey, HDAccountOps, HDAccountStorageOps, HDAddressOps, - HDWalletOps}; - use crypto::Secp256k1ExtendedPublicKey; + HDCoinExtendedPubkey, HDWalletOps}; pub(crate) async fn enable_hd_account( coin: &Coin, @@ -468,7 +465,7 @@ pub mod common_impl { path_to_address: &HDPathAccountToAddressId, ) -> MmResult>, EnableCoinBalanceError> where - Coin: ExtractExtendedPubkey + Coin: ExtractExtendedPubkey> + HDWalletBalanceOps + MarketCoinOps + Sync, diff --git a/mm2src/coins/coin_errors.rs b/mm2src/coins/coin_errors.rs index 34b0c46486..6075317bfc 100644 --- a/mm2src/coins/coin_errors.rs +++ b/mm2src/coins/coin_errors.rs @@ -1,11 +1,12 @@ -use crate::eth::nft_swap_v2::errors::{Erc721FunctionError, HtlcParamsError, PaymentStatusErr, PrepareTxDataError}; +use crate::eth::eth_swap_v2::{PaymentStatusErr, PrepareTxDataError, ValidatePaymentV2Err}; +use crate::eth::nft_swap_v2::errors::{Erc721FunctionError, HtlcParamsError}; use crate::eth::{EthAssocTypesError, EthNftAssocTypesError, Web3RpcError}; use crate::{utxo::rpc_clients::UtxoRpcError, NumConversError, UnexpectedDerivationMethod}; use enum_derives::EnumFromStringify; use futures01::Future; use mm2_err_handle::prelude::MmError; use spv_validation::helpers_validation::SPVError; -use std::num::TryFromIntError; +use std::{array::TryFromSliceError, num::TryFromIntError}; /// Helper type used as result for swap payment validation function(s) pub type ValidatePaymentFut = Box> + Send>; @@ -23,7 +24,9 @@ pub enum ValidatePaymentError { "NumConversError", "UnexpectedDerivationMethod", "keys::Error", - "PrepareTxDataError" + "PrepareTxDataError", + "ethabi::Error", + "TryFromSliceError" )] InternalError(String), /// Problem with deserializing the transaction, or one of the transaction parts is invalid. @@ -48,8 +51,8 @@ pub enum ValidatePaymentError { WatcherRewardError(String), /// Input payment timelock overflows the type used by specific coin. TimelockOverflow(TryFromIntError), - #[display(fmt = "Nft Protocol is not supported yet!")] - NftProtocolNotSupported, + ProtocolNotSupported(String), + InvalidData(String), } impl From for ValidatePaymentError { @@ -75,7 +78,9 @@ impl From for ValidatePaymentError { | Web3RpcError::Timeout(internal) | Web3RpcError::NumConversError(internal) | Web3RpcError::InvalidGasApiConfig(internal) => ValidatePaymentError::InternalError(internal), - Web3RpcError::NftProtocolNotSupported => ValidatePaymentError::NftProtocolNotSupported, + Web3RpcError::NftProtocolNotSupported => { + ValidatePaymentError::ProtocolNotSupported("Nft protocol is not supported".to_string()) + }, } } } @@ -84,9 +89,8 @@ impl From for ValidatePaymentError { fn from(err: PaymentStatusErr) -> Self { match err { PaymentStatusErr::Transport(e) => Self::Transport(e), - PaymentStatusErr::AbiError(e) - | PaymentStatusErr::Internal(e) - | PaymentStatusErr::TxDeserializationError(e) => Self::InternalError(e), + PaymentStatusErr::ABIError(e) | PaymentStatusErr::Internal(e) => Self::InternalError(e), + PaymentStatusErr::InvalidData(e) => Self::InvalidData(e), } } } @@ -95,7 +99,16 @@ impl From for ValidatePaymentError { fn from(err: HtlcParamsError) -> Self { match err { HtlcParamsError::WrongPaymentTx(e) => ValidatePaymentError::WrongPaymentTx(e), - HtlcParamsError::TxDeserializationError(e) => ValidatePaymentError::TxDeserializationError(e), + HtlcParamsError::ABIError(e) | HtlcParamsError::InvalidData(e) => ValidatePaymentError::InvalidData(e), + } + } +} + +impl From for ValidatePaymentError { + fn from(err: ValidatePaymentV2Err) -> Self { + match err { + ValidatePaymentV2Err::UnexpectedPaymentState(e) => ValidatePaymentError::UnexpectedPaymentState(e), + ValidatePaymentV2Err::WrongPaymentTx(e) => ValidatePaymentError::WrongPaymentTx(e), } } } diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index dfdac1304a..b8db8c7c12 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -24,11 +24,11 @@ use super::eth::Action::{Call, Create}; use super::watcher_common::{validate_watcher_reward, REWARD_GAS_AMOUNT}; use super::*; use crate::coin_balance::{EnableCoinBalanceError, EnabledCoinBalanceParams, HDAccountBalance, HDAddressBalance, - HDBalanceAddress, HDWalletBalance, HDWalletBalanceOps}; + HDWalletBalance, HDWalletBalanceOps}; use crate::eth::eth_rpc::ETH_RPC_REQUEST_TIMEOUT; use crate::eth::web3_transport::websocket_transport::{WebsocketTransport, WebsocketTransportNode}; -use crate::hd_wallet::{HDAccountOps, HDCoinAddress, HDCoinHDAccount, HDCoinHDAddress, HDCoinWithdrawOps, - HDConfirmAddress, HDPathAccountToAddressId, HDWalletCoinOps, HDXPubExtractor}; +use crate::hd_wallet::{HDAccountOps, HDCoinAddress, HDCoinWithdrawOps, HDConfirmAddress, HDPathAccountToAddressId, + HDWalletCoinOps, HDXPubExtractor}; use crate::lp_price::get_base_price_in_rel; use crate::nft::nft_errors::ParseContractTypeError; use crate::nft::nft_structs::{ContractType, ConvertChain, NftInfo, TransactionNftDetails, WithdrawErc1155, @@ -48,8 +48,8 @@ use crate::rpc_command::{account_balance, get_new_address, init_account_balance, init_scan_for_new_addresses}; use crate::{coin_balance, scan_for_new_addresses_impl, BalanceResult, CoinWithDerivationMethod, DerivationMethod, DexFee, Eip1559Ops, MakerNftSwapOpsV2, ParseCoinAssocTypes, ParseNftAssocTypes, PayForGasParams, - PrivKeyPolicy, RefundMakerPaymentArgs, RpcCommonOps, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, - ToBytes, ValidateNftMakerPaymentArgs, ValidateWatcherSpendInput, WatcherSpendType}; + PrivKeyPolicy, RpcCommonOps, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, ToBytes, + ValidateNftMakerPaymentArgs, ValidateWatcherSpendInput, WatcherSpendType}; use async_trait::async_trait; use bitcrypto::{dhash160, keccak256, ripemd160, sha256}; use common::custom_futures::repeatable::{Ready, Retry, RetryOnError}; @@ -58,7 +58,7 @@ use common::executor::{abortable_queue::AbortableQueue, AbortOnDropHandle, Abort AbortedError, SpawnAbortable, Timer}; use common::log::{debug, error, info, warn}; use common::number_type_casting::SafeTypeCastingNumbers; -use common::{get_utc_timestamp, now_sec, small_rng, DEX_FEE_ADDR_RAW_PUBKEY}; +use common::{now_sec, small_rng, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::key_pair_from_secret; use crypto::{Bip44Chain, CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy}; use derive_more::Display; @@ -76,10 +76,8 @@ use futures::future::{join, join_all, select_ok, try_join_all, Either, FutureExt use futures01::Future; use http::Uri; use instant::Instant; -use keys::Public as HtlcPubKey; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_event_stream::behaviour::{EventBehaviour, EventInitStatus}; -use mm2_net::transport::{GuiAuthValidation, GuiAuthValidationGenerator}; use mm2_number::bigdecimal_custom::CheckedDivision; use mm2_number::{BigDecimal, BigUint, MmNumber}; #[cfg(test)] use mocktopus::macros::*; @@ -160,6 +158,12 @@ pub(crate) use eip1559_gas_fee::FeePerGasEstimated; use eip1559_gas_fee::{BlocknativeGasApiCaller, FeePerGasSimpleEstimator, GasApiConfig, GasApiProvider, InfuraGasApiCaller}; +pub mod erc20; +use erc20::get_token_decimals; + +pub(crate) mod eth_swap_v2; +use eth_swap_v2::{EthPaymentType, PaymentMethod}; + /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol /// Dev chain (195.201.137.5:8565) contract address: 0x83965C539899cC0F918552e5A26915de40ee8852 /// Ropsten: https://ropsten.etherscan.io/address/0x7bc1bbdd6a0a722fc9bffc49c921b685ecb84b94 @@ -172,6 +176,9 @@ const ERC721_ABI: &str = include_str!("eth/erc721_abi.json"); /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md const ERC1155_ABI: &str = include_str!("eth/erc1155_abi.json"); const NFT_SWAP_CONTRACT_ABI: &str = include_str!("eth/nft_swap_contract_abi.json"); +const NFT_MAKER_SWAP_V2_ABI: &str = include_str!("eth/nft_maker_swap_v2_abi.json"); +const MAKER_SWAP_V2_ABI: &str = include_str!("eth/maker_swap_v2_abi.json"); +const TAKER_SWAP_V2_ABI: &str = include_str!("eth/taker_swap_v2_abi.json"); /// Payment states from etomic swap smart contract: https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol#L5 pub enum PaymentState { @@ -222,7 +229,7 @@ const GAS_PRICE_APPROXIMATION_PERCENT_ON_ORDER_ISSUE: u64 = 5; /// - it may increase by 3% during the swap. const GAS_PRICE_APPROXIMATION_PERCENT_ON_TRADE_PREIMAGE: u64 = 7; -/// Heuristic gas limits for withdraw and swap operations (for swaps also including extra margin value for possible changes in opcodes gas) +/// Heuristic default gas limits for withdraw and swap operations (including extra margin value for possible changes in opcodes cost) pub mod gas_limit { /// Gas limit for sending coins pub const ETH_SEND_COINS: u64 = 21_000; @@ -233,34 +240,283 @@ pub mod gas_limit { /// real values are approx 48,6K by etherscan pub const ETH_PAYMENT: u64 = 65_000; /// Gas limit for swap payment tx with ERC20 tokens - /// real values are 98,9K - pub const ERC20_PAYMENT: u64 = 120_000; + /// real values are 98,9K for ERC20 and 135K for ERC-1967 proxied ERC20 contracts (use 'gas_limit' override in coins to tune) + pub const ERC20_PAYMENT: u64 = 150_000; /// Gas limit for swap receiver spend tx with coins /// real values are 40,7K pub const ETH_RECEIVER_SPEND: u64 = 65_000; /// Gas limit for swap receiver spend tx with ERC20 tokens /// real values are 72,8K - pub const ERC20_RECEIVER_SPEND: u64 = 120_000; + pub const ERC20_RECEIVER_SPEND: u64 = 150_000; /// Gas limit for swap refund tx with coins pub const ETH_SENDER_REFUND: u64 = 100_000; - /// Gas limit for swap refund tx with with ERC20 tokens + /// Gas limit for swap refund tx with ERC20 tokens pub const ERC20_SENDER_REFUND: u64 = 150_000; /// Gas limit for other operations pub const ETH_MAX_TRADE_GAS: u64 = 150_000; } -/// Lifetime of generated signed message for gui-auth requests -const GUI_AUTH_SIGNED_MESSAGE_LIFETIME_SEC: i64 = 90; +/// Default gas limits for EthGasLimitV2 +pub mod gas_limit_v2 { + /// Gas limits for maker operations in EtomicSwapMakerV2 contract + pub mod maker { + pub const ETH_PAYMENT: u64 = 65_000; + pub const ERC20_PAYMENT: u64 = 150_000; + pub const ETH_TAKER_SPEND: u64 = 100_000; + pub const ERC20_TAKER_SPEND: u64 = 150_000; + pub const ETH_MAKER_REFUND_TIMELOCK: u64 = 90_000; + pub const ERC20_MAKER_REFUND_TIMELOCK: u64 = 100_000; + pub const ETH_MAKER_REFUND_SECRET: u64 = 90_000; + pub const ERC20_MAKER_REFUND_SECRET: u64 = 100_000; + } + + /// Gas limits for taker operations in EtomicSwapTakerV2 contract + pub mod taker { + pub const ETH_PAYMENT: u64 = 65_000; + pub const ERC20_PAYMENT: u64 = 150_000; + pub const ETH_MAKER_SPEND: u64 = 100_000; + pub const ERC20_MAKER_SPEND: u64 = 115_000; + pub const ETH_TAKER_REFUND_TIMELOCK: u64 = 90_000; + pub const ERC20_TAKER_REFUND_TIMELOCK: u64 = 100_000; + pub const ETH_TAKER_REFUND_SECRET: u64 = 90_000; + pub const ERC20_TAKER_REFUND_SECRET: u64 = 100_000; + pub const APPROVE_PAYMENT: u64 = 50_000; + } + + pub mod nft_maker { + pub const ERC721_PAYMENT: u64 = 130_000; + pub const ERC1155_PAYMENT: u64 = 130_000; + pub const ERC721_TAKER_SPEND: u64 = 100_000; + pub const ERC1155_TAKER_SPEND: u64 = 100_000; + pub const ERC721_MAKER_REFUND_TIMELOCK: u64 = 100_000; + pub const ERC1155_MAKER_REFUND_TIMELOCK: u64 = 100_000; + pub const ERC721_MAKER_REFUND_SECRET: u64 = 100_000; + pub const ERC1155_MAKER_REFUND_SECRET: u64 = 100_000; + } +} + +/// Coin conf param to override default gas limits +#[derive(Deserialize)] +#[serde(default)] +pub struct EthGasLimit { + /// Gas limit for sending coins + pub eth_send_coins: u64, + /// Gas limit for sending ERC20 tokens + pub eth_send_erc20: u64, + /// Gas limit for swap payment tx with coins + pub eth_payment: u64, + /// Gas limit for swap payment tx with ERC20 tokens + pub erc20_payment: u64, + /// Gas limit for swap receiver spend tx with coins + pub eth_receiver_spend: u64, + /// Gas limit for swap receiver spend tx with ERC20 tokens + pub erc20_receiver_spend: u64, + /// Gas limit for swap refund tx with coins + pub eth_sender_refund: u64, + /// Gas limit for swap refund tx with ERC20 tokens + pub erc20_sender_refund: u64, + /// Gas limit for other operations + pub eth_max_trade_gas: u64, +} + +impl Default for EthGasLimit { + fn default() -> Self { + EthGasLimit { + eth_send_coins: gas_limit::ETH_SEND_COINS, + eth_send_erc20: gas_limit::ETH_SEND_ERC20, + eth_payment: gas_limit::ETH_PAYMENT, + erc20_payment: gas_limit::ERC20_PAYMENT, + eth_receiver_spend: gas_limit::ETH_RECEIVER_SPEND, + erc20_receiver_spend: gas_limit::ERC20_RECEIVER_SPEND, + eth_sender_refund: gas_limit::ETH_SENDER_REFUND, + erc20_sender_refund: gas_limit::ERC20_SENDER_REFUND, + eth_max_trade_gas: gas_limit::ETH_MAX_TRADE_GAS, + } + } +} + +#[derive(Default, Deserialize)] +#[serde(default)] +pub struct EthGasLimitV2 { + pub maker: MakerGasLimitV2, + pub taker: TakerGasLimitV2, + pub nft_maker: NftMakerGasLimitV2, +} + +#[derive(Deserialize)] +#[serde(default)] +pub struct MakerGasLimitV2 { + pub eth_payment: u64, + pub erc20_payment: u64, + pub eth_taker_spend: u64, + pub erc20_taker_spend: u64, + pub eth_maker_refund_timelock: u64, + pub erc20_maker_refund_timelock: u64, + pub eth_maker_refund_secret: u64, + pub erc20_maker_refund_secret: u64, +} + +#[derive(Deserialize)] +#[serde(default)] +pub struct TakerGasLimitV2 { + pub eth_payment: u64, + pub erc20_payment: u64, + pub eth_maker_spend: u64, + pub erc20_maker_spend: u64, + pub eth_taker_refund_timelock: u64, + pub erc20_taker_refund_timelock: u64, + pub eth_taker_refund_secret: u64, + pub erc20_taker_refund_secret: u64, + pub approve_payment: u64, +} + +#[derive(Deserialize)] +#[serde(default)] +pub struct NftMakerGasLimitV2 { + pub erc721_payment: u64, + pub erc1155_payment: u64, + pub erc721_taker_spend: u64, + pub erc1155_taker_spend: u64, + pub erc721_maker_refund_timelock: u64, + pub erc1155_maker_refund_timelock: u64, + pub erc721_maker_refund_secret: u64, + pub erc1155_maker_refund_secret: u64, +} + +impl EthGasLimitV2 { + fn gas_limit( + &self, + coin_type: &EthCoinType, + payment_type: EthPaymentType, + method: PaymentMethod, + ) -> Result { + match coin_type { + EthCoinType::Eth => { + let gas_limit = match payment_type { + EthPaymentType::MakerPayments => match method { + PaymentMethod::Send => self.maker.eth_payment, + PaymentMethod::Spend => self.maker.eth_taker_spend, + PaymentMethod::RefundTimelock => self.maker.eth_maker_refund_timelock, + PaymentMethod::RefundSecret => self.maker.eth_maker_refund_secret, + }, + EthPaymentType::TakerPayments => match method { + PaymentMethod::Send => self.taker.eth_payment, + PaymentMethod::Spend => self.taker.eth_maker_spend, + PaymentMethod::RefundTimelock => self.taker.eth_taker_refund_timelock, + PaymentMethod::RefundSecret => self.taker.eth_taker_refund_secret, + }, + }; + Ok(gas_limit) + }, + EthCoinType::Erc20 { .. } => { + let gas_limit = match payment_type { + EthPaymentType::MakerPayments => match method { + PaymentMethod::Send => self.maker.erc20_payment, + PaymentMethod::Spend => self.maker.erc20_taker_spend, + PaymentMethod::RefundTimelock => self.maker.erc20_maker_refund_timelock, + PaymentMethod::RefundSecret => self.maker.erc20_maker_refund_secret, + }, + EthPaymentType::TakerPayments => match method { + PaymentMethod::Send => self.taker.erc20_payment, + PaymentMethod::Spend => self.taker.erc20_maker_spend, + PaymentMethod::RefundTimelock => self.taker.erc20_taker_refund_timelock, + PaymentMethod::RefundSecret => self.taker.erc20_taker_refund_secret, + }, + }; + Ok(gas_limit) + }, + EthCoinType::Nft { .. } => Err("NFT protocol is not supported for ETH and ERC20 Swaps".to_string()), + } + } + + fn nft_gas_limit(&self, contract_type: &ContractType, method: PaymentMethod) -> u64 { + match contract_type { + ContractType::Erc1155 => match method { + PaymentMethod::Send => self.nft_maker.erc1155_payment, + PaymentMethod::Spend => self.nft_maker.erc1155_taker_spend, + PaymentMethod::RefundTimelock => self.nft_maker.erc1155_maker_refund_timelock, + PaymentMethod::RefundSecret => self.nft_maker.erc1155_maker_refund_secret, + }, + ContractType::Erc721 => match method { + PaymentMethod::Send => self.nft_maker.erc721_payment, + PaymentMethod::Spend => self.nft_maker.erc721_taker_spend, + PaymentMethod::RefundTimelock => self.nft_maker.erc721_maker_refund_timelock, + PaymentMethod::RefundSecret => self.nft_maker.erc721_maker_refund_secret, + }, + } + } +} + +impl Default for MakerGasLimitV2 { + fn default() -> Self { + MakerGasLimitV2 { + eth_payment: gas_limit_v2::maker::ETH_PAYMENT, + erc20_payment: gas_limit_v2::maker::ERC20_PAYMENT, + eth_taker_spend: gas_limit_v2::maker::ETH_TAKER_SPEND, + erc20_taker_spend: gas_limit_v2::maker::ERC20_TAKER_SPEND, + eth_maker_refund_timelock: gas_limit_v2::maker::ETH_MAKER_REFUND_TIMELOCK, + erc20_maker_refund_timelock: gas_limit_v2::maker::ERC20_MAKER_REFUND_TIMELOCK, + eth_maker_refund_secret: gas_limit_v2::maker::ETH_MAKER_REFUND_SECRET, + erc20_maker_refund_secret: gas_limit_v2::maker::ERC20_MAKER_REFUND_SECRET, + } + } +} + +impl Default for TakerGasLimitV2 { + fn default() -> Self { + TakerGasLimitV2 { + eth_payment: gas_limit_v2::taker::ETH_PAYMENT, + erc20_payment: gas_limit_v2::taker::ERC20_PAYMENT, + eth_maker_spend: gas_limit_v2::taker::ETH_MAKER_SPEND, + erc20_maker_spend: gas_limit_v2::taker::ERC20_MAKER_SPEND, + eth_taker_refund_timelock: gas_limit_v2::taker::ETH_TAKER_REFUND_TIMELOCK, + erc20_taker_refund_timelock: gas_limit_v2::taker::ERC20_TAKER_REFUND_TIMELOCK, + eth_taker_refund_secret: gas_limit_v2::taker::ETH_TAKER_REFUND_SECRET, + erc20_taker_refund_secret: gas_limit_v2::taker::ERC20_TAKER_REFUND_SECRET, + approve_payment: gas_limit_v2::taker::APPROVE_PAYMENT, + } + } +} + +impl Default for NftMakerGasLimitV2 { + fn default() -> Self { + NftMakerGasLimitV2 { + erc721_payment: gas_limit_v2::nft_maker::ERC721_PAYMENT, + erc1155_payment: gas_limit_v2::nft_maker::ERC1155_PAYMENT, + erc721_taker_spend: gas_limit_v2::nft_maker::ERC721_TAKER_SPEND, + erc1155_taker_spend: gas_limit_v2::nft_maker::ERC1155_TAKER_SPEND, + erc721_maker_refund_timelock: gas_limit_v2::nft_maker::ERC721_MAKER_REFUND_TIMELOCK, + erc1155_maker_refund_timelock: gas_limit_v2::nft_maker::ERC1155_MAKER_REFUND_TIMELOCK, + erc721_maker_refund_secret: gas_limit_v2::nft_maker::ERC721_MAKER_REFUND_SECRET, + erc1155_maker_refund_secret: gas_limit_v2::nft_maker::ERC1155_MAKER_REFUND_SECRET, + } + } +} + +trait ExtractGasLimit: Default + for<'de> Deserialize<'de> { + fn key() -> &'static str; +} + +impl ExtractGasLimit for EthGasLimit { + fn key() -> &'static str { "gas_limit" } +} + +impl ExtractGasLimit for EthGasLimitV2 { + fn key() -> &'static str { "gas_limit_v2" } +} /// Max transaction type according to EIP-2718 const ETH_MAX_TX_TYPE: u64 = 0x7f; lazy_static! { pub static ref SWAP_CONTRACT: Contract = Contract::load(SWAP_CONTRACT_ABI.as_bytes()).unwrap(); + pub static ref MAKER_SWAP_V2: Contract = Contract::load(MAKER_SWAP_V2_ABI.as_bytes()).unwrap(); + pub static ref TAKER_SWAP_V2: Contract = Contract::load(TAKER_SWAP_V2_ABI.as_bytes()).unwrap(); pub static ref ERC20_CONTRACT: Contract = Contract::load(ERC20_ABI.as_bytes()).unwrap(); pub static ref ERC721_CONTRACT: Contract = Contract::load(ERC721_ABI.as_bytes()).unwrap(); pub static ref ERC1155_CONTRACT: Contract = Contract::load(ERC1155_ABI.as_bytes()).unwrap(); pub static ref NFT_SWAP_CONTRACT: Contract = Contract::load(NFT_SWAP_CONTRACT_ABI.as_bytes()).unwrap(); + pub static ref NFT_MAKER_SWAP_V2: Contract = Contract::load(NFT_MAKER_SWAP_V2_ABI.as_bytes()).unwrap(); } pub type EthDerivationMethod = DerivationMethod; @@ -268,20 +524,6 @@ pub type Web3RpcFut = Box> pub type Web3RpcResult = Result>; type EthPrivKeyPolicy = PrivKeyPolicy; -#[macro_export] -macro_rules! wei_from_gwei_decimal { - ($big_decimal: expr) => { - $crate::eth::wei_from_big_decimal($big_decimal, $crate::eth::ETH_GWEI_DECIMALS) - }; -} - -#[macro_export] -macro_rules! wei_to_gwei_decimal { - ($gwei: expr) => { - $crate::eth::u256_to_big_decimal($gwei, $crate::eth::ETH_GWEI_DECIMALS) - }; -} - #[derive(Clone, Debug)] pub(crate) struct LegacyGasPrice { pub(crate) gas_price: U256, @@ -326,11 +568,11 @@ impl TryFrom for PayForGasOption { fn try_from(param: PayForGasParams) -> Result { match param { PayForGasParams::Legacy(legacy) => Ok(Self::Legacy(LegacyGasPrice { - gas_price: wei_from_gwei_decimal!(&legacy.gas_price)?, + gas_price: wei_from_gwei_decimal(&legacy.gas_price)?, })), PayForGasParams::Eip1559(eip1559) => Ok(Self::Eip1559(Eip1559FeePerGas { - max_fee_per_gas: wei_from_gwei_decimal!(&eip1559.max_fee_per_gas)?, - max_priority_fee_per_gas: wei_from_gwei_decimal!(&eip1559.max_priority_fee_per_gas)?, + max_fee_per_gas: wei_from_gwei_decimal(&eip1559.max_fee_per_gas)?, + max_priority_fee_per_gas: wei_from_gwei_decimal(&eip1559.max_priority_fee_per_gas)?, })), } } @@ -342,11 +584,11 @@ type GasDetails = (U256, PayForGasOption); pub enum Web3RpcError { #[display(fmt = "Transport: {}", _0)] Transport(String), - #[from_stringify("serde_json::Error")] #[display(fmt = "Invalid response: {}", _0)] InvalidResponse(String), #[display(fmt = "Timeout: {}", _0)] Timeout(String), + #[from_stringify("serde_json::Error")] #[display(fmt = "Internal: {}", _0)] Internal(String), #[display(fmt = "Invalid gas api provider config: {}", _0)] @@ -599,7 +841,7 @@ pub(crate) enum FeeEstimatorState { pub struct EthCoinImpl { ticker: String, pub coin_type: EthCoinType, - priv_key_policy: EthPrivKeyPolicy, + pub(crate) priv_key_policy: EthPrivKeyPolicy, /// Either an Iguana address or a 'EthHDWallet' instance. /// Arc is used to use the same hd wallet from platform coin if we need to. /// This allows the reuse of the same derived accounts/addresses of the @@ -607,6 +849,7 @@ pub struct EthCoinImpl { derivation_method: Arc, sign_message_prefix: Option, swap_contract_address: Address, + swap_v2_contracts: Option, fallback_swap_contract: Option
, contract_supports_watchers: bool, web3_instances: AsyncMutex>, @@ -629,13 +872,17 @@ pub struct EthCoinImpl { /// and unlocked once the transaction is confirmed. This prevents nonce conflicts when multiple transactions /// are initiated concurrently from the same address. address_nonce_locks: Arc>>>>, - erc20_tokens_infos: Arc>>, + erc20_tokens_infos: Arc>>, /// Stores information about NFTs owned by the user. Each entry in the HashMap is uniquely identified by a composite key /// consisting of the token address and token ID, separated by a comma. This field is essential for tracking the NFT assets /// information (chain & contract type, amount etc.), where ownership and amount, in ERC1155 case, might change over time. pub nfts_infos: Arc>>, /// Context for eth fee per gas estimator loop. Created if coin supports fee per gas estimation pub(crate) platform_fee_estimator_state: Arc, + /// Config provided gas limits for swap and send transactions + pub(crate) gas_limit: EthGasLimit, + /// Config provided gas limits v2 for swap v2 transactions + pub(crate) gas_limit_v2: EthGasLimitV2, /// This spawner is used to spawn coin's related futures that should be aborted on coin deactivation /// and on [`MmArc::stop`]. pub abortable_system: AbortableQueue, @@ -649,7 +896,7 @@ pub struct Web3Instance { /// Information about a token that follows the ERC20 protocol on an EVM-based network. #[derive(Clone, Debug)] -pub struct Erc20TokenInfo { +pub struct Erc20TokenDetails { /// The contract address of the token on the EVM-based network. pub token_address: Address, /// The number of decimal places the token uses. @@ -657,6 +904,13 @@ pub struct Erc20TokenInfo { pub decimals: u8, } +#[derive(Copy, Clone, Deserialize)] +pub struct SwapV2Contracts { + pub maker_swap_v2_contract: Address, + pub taker_swap_v2_contract: Address, + pub nft_maker_swap_v2_contract: Address, +} + #[derive(Deserialize, Serialize)] #[serde(tag = "format")] pub enum EthAddressFormat { @@ -775,9 +1029,18 @@ impl EthCoinImpl { /// The id used to differentiate payments on Etomic swap smart contract pub(crate) fn etomic_swap_id(&self, time_lock: u32, secret_hash: &[u8]) -> Vec { let timelock_bytes = time_lock.to_le_bytes(); + self.generate_etomic_swap_id(&timelock_bytes, secret_hash) + } - let mut input = Vec::with_capacity(timelock_bytes.len() + secret_hash.len()); - input.extend_from_slice(&timelock_bytes); + /// The id used to differentiate payments on Etomic swap v2 smart contracts + pub(crate) fn etomic_swap_id_v2(&self, time_lock: u64, secret_hash: &[u8]) -> Vec { + let timelock_bytes = time_lock.to_le_bytes(); + self.generate_etomic_swap_id(&timelock_bytes, secret_hash) + } + + fn generate_etomic_swap_id(&self, time_lock_bytes: &[u8], secret_hash: &[u8]) -> Vec { + let mut input = Vec::with_capacity(time_lock_bytes.len() + secret_hash.len()); + input.extend_from_slice(time_lock_bytes); input.extend_from_slice(secret_hash); sha256(&input).to_vec() } @@ -794,17 +1057,20 @@ impl EthCoinImpl { } } - pub fn add_erc_token_info(&self, ticker: String, info: Erc20TokenInfo) { + pub fn add_erc_token_info(&self, ticker: String, info: Erc20TokenDetails) { self.erc20_tokens_infos.lock().unwrap().insert(ticker, info); } /// # Warning /// Be very careful using this function since it returns dereferenced clone /// of value behind the MutexGuard and makes it non-thread-safe. - pub fn get_erc_tokens_infos(&self) -> HashMap { + pub fn get_erc_tokens_infos(&self) -> HashMap { let guard = self.erc20_tokens_infos.lock().unwrap(); (*guard).clone() } + + #[inline(always)] + pub fn chain_id(&self) -> u64 { self.chain_id } } async fn get_raw_transaction_impl(coin: EthCoin, req: RawTransactionRequest) -> RawTransactionResult { @@ -851,20 +1117,20 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit get_valid_nft_addr_to_withdraw(coin, &withdraw_type.to, &withdraw_type.token_address)?; let token_id_str = &withdraw_type.token_id.to_string(); - let wallet_amount = eth_coin.erc1155_balance(token_addr, token_id_str).await?; + let wallet_erc1155_amount = eth_coin.erc1155_balance(token_addr, token_id_str).await?; - let amount_dec = if withdraw_type.max { - wallet_amount.clone() + let amount_uint = if withdraw_type.max { + wallet_erc1155_amount.clone() } else { - withdraw_type.amount.unwrap_or_else(|| 1.into()) + withdraw_type.amount.unwrap_or_else(|| BigUint::from(1u32)) }; - if amount_dec > wallet_amount { + if amount_uint > wallet_erc1155_amount { return MmError::err(WithdrawError::NotEnoughNftsAmount { token_address: withdraw_type.token_address, token_id: withdraw_type.token_id.to_string(), - available: wallet_amount, - required: amount_dec, + available: wallet_erc1155_amount, + required: amount_uint, }); } @@ -875,7 +1141,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit let token_id_u256 = U256::from_dec_str(token_id_str).map_to_mm(|e| NumConversError::new(format!("{:?}", e)))?; let amount_u256 = - U256::from_dec_str(&amount_dec.to_string()).map_to_mm(|e| NumConversError::new(format!("{:?}", e)))?; + U256::from_dec_str(&amount_uint.to_string()).map_to_mm(|e| NumConversError::new(format!("{:?}", e)))?; let data = function.encode_input(&[ Token::Address(my_address), Token::Address(to_addr), @@ -927,14 +1193,14 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit let fee_details = EthTxFeeDetails::new(gas, pay_for_gas_option, fee_coin)?; Ok(TransactionNftDetails { - tx_hex: BytesJson::from(signed_bytes.to_vec()), - tx_hash: format!("{:02x}", signed.tx_hash_as_bytes()), + tx_hex: BytesJson::from(signed_bytes.to_vec()), // TODO: should we return tx_hex 0x-prefixed (everywhere)? + tx_hash: format!("{:02x}", signed.tx_hash_as_bytes()), // TODO: add 0x hash (use unified hash format for eth wherever it is returned) from: vec![eth_coin.my_address()?], to: vec![withdraw_type.to], contract_type: ContractType::Erc1155, token_address: withdraw_type.token_address, token_id: withdraw_type.token_id, - amount: amount_dec, + amount: amount_uint, fee_details: Some(fee_details.into()), coin: eth_coin.ticker.clone(), block_height: 0, @@ -1019,13 +1285,13 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd Ok(TransactionNftDetails { tx_hex: BytesJson::from(signed_bytes.to_vec()), - tx_hash: format!("{:02x}", signed.tx_hash_as_bytes()), + tx_hash: format!("{:02x}", signed.tx_hash_as_bytes()), // TODO: add 0x hash (use unified hash format for eth wherever it is returned) from: vec![eth_coin.my_address()?], to: vec![withdraw_type.to], contract_type: ContractType::Erc721, token_address: withdraw_type.token_address, token_id: withdraw_type.token_id, - amount: 1.into(), + amount: BigUint::from(1u8), fee_details: Some(fee_details.into()), coin: eth_coin.ticker.clone(), block_height: 0, @@ -1044,30 +1310,35 @@ impl Deref for EthCoin { #[async_trait] impl SwapOps for EthCoin { - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { - let address = try_tx_fus!(addr_from_raw_pubkey(fee_addr)); - - Box::new( - self.send_to_address( - address, - try_tx_fus!(wei_from_big_decimal(&dex_fee.fee_amount().into(), self.decimals)), - ) - .map(TransactionEnum::from), + async fn send_taker_fee( + &self, + fee_addr: &[u8], + dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { + let address = try_tx_s!(addr_from_raw_pubkey(fee_addr)); + self.send_to_address( + address, + try_tx_s!(wei_from_big_decimal(&dex_fee.fee_amount().into(), self.decimals)), ) + .map(TransactionEnum::from) + .compat() + .await } - fn send_maker_payment(&self, maker_payment: SendPaymentArgs) -> TransactionFut { - Box::new( - self.send_hash_time_locked_payment(maker_payment) - .map(TransactionEnum::from), - ) + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + self.send_hash_time_locked_payment(maker_payment_args) + .compat() + .await + .map(TransactionEnum::from) } - fn send_taker_payment(&self, taker_payment: SendPaymentArgs) -> TransactionFut { - Box::new( - self.send_hash_time_locked_payment(taker_payment) - .map(TransactionEnum::from), - ) + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + self.send_hash_time_locked_payment(taker_payment_args) + .map(TransactionEnum::from) + .compat() + .await } async fn send_maker_spends_taker_payment( @@ -1100,10 +1371,15 @@ impl SwapOps for EthCoin { .map(TransactionEnum::from) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::SignedEthTx(t) => t.clone(), - _ => panic!(), + fee_tx => { + return MmError::err(ValidatePaymentError::InternalError(format!( + "Invalid fee tx type. fee tx: {:?}", + fee_tx + ))) + }, }; validate_fee_impl(self.clone(), EthValidateFeeArgs { fee_tx_hash: &tx.tx_hash(), @@ -1113,6 +1389,8 @@ impl SwapOps for EthCoin { min_block_number: validate_fee_args.min_block_number, uuid: validate_fee_args.uuid, }) + .compat() + .await } #[inline] @@ -1125,70 +1403,62 @@ impl SwapOps for EthCoin { self.validate_payment(input).compat().await } - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { - let id = self.etomic_swap_id( - try_fus!(if_my_payment_sent_args.time_lock.try_into()), - if_my_payment_sent_args.secret_hash, - ); - let swap_contract_address = try_fus!(if_my_payment_sent_args.swap_contract_address.try_to_address()); - let selfi = self.clone(); + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { + let time_lock = if_my_payment_sent_args + .time_lock + .try_into() + .map_err(|e: TryFromIntError| e.to_string())?; + let id = self.etomic_swap_id(time_lock, if_my_payment_sent_args.secret_hash); + let swap_contract_address = if_my_payment_sent_args.swap_contract_address.try_to_address()?; let from_block = if_my_payment_sent_args.search_from_block; - let fut = async move { - let status = try_s!( - selfi - .payment_status(swap_contract_address, Token::FixedBytes(id.clone())) - .compat() - .await - ); + let status = self + .payment_status(swap_contract_address, Token::FixedBytes(id.clone())) + .compat() + .await?; - if status == U256::from(PaymentState::Uninitialized as u8) { - return Ok(None); - }; + if status == U256::from(PaymentState::Uninitialized as u8) { + return Ok(None); + }; - let mut current_block = try_s!(selfi.current_block().compat().await); - if current_block < from_block { - current_block = from_block; - } + let mut current_block = self.current_block().compat().await?; + if current_block < from_block { + current_block = from_block; + } - let mut from_block = from_block; + let mut from_block = from_block; - loop { - let to_block = current_block.min(from_block + selfi.logs_block_range); + loop { + let to_block = current_block.min(from_block + self.logs_block_range); - let events = try_s!( - selfi - .payment_sent_events(swap_contract_address, from_block, to_block) - .compat() - .await - ); + let events = self + .payment_sent_events(swap_contract_address, from_block, to_block) + .compat() + .await?; - let found = events.iter().find(|event| &event.data.0[..32] == id.as_slice()); + let found = events.iter().find(|event| &event.data.0[..32] == id.as_slice()); - match found { - Some(event) => { - let transaction = try_s!( - selfi - .transaction(TransactionId::Hash(event.transaction_hash.unwrap())) - .await - ); - match transaction { - Some(t) => break Ok(Some(try_s!(signed_tx_from_web3_tx(t)).into())), - None => break Ok(None), - } - }, - None => { - if to_block >= current_block { - break Ok(None); - } - from_block = to_block; - }, - } + match found { + Some(event) => { + let transaction = try_s!( + self.transaction(TransactionId::Hash(event.transaction_hash.unwrap())) + .await + ); + match transaction { + Some(t) => break Ok(Some(try_s!(signed_tx_from_web3_tx(t)).into())), + None => break Ok(None), + } + }, + None => { + if to_block >= current_block { + break Ok(None); + } + from_block = to_block; + }, } - }; - Box::new(fut.boxed().compat()) + } } async fn search_for_swap_tx_spend_my( @@ -1698,7 +1968,11 @@ impl WatcherOps for EthCoin { ))); } }, - EthCoinType::Nft { .. } => return MmError::err(ValidatePaymentError::NftProtocolNotSupported), + EthCoinType::Nft { .. } => { + return MmError::err(ValidatePaymentError::ProtocolNotSupported( + "Nft protocol is not supported by watchers yet".to_string(), + )) + }, } Ok(()) @@ -1939,7 +2213,11 @@ impl WatcherOps for EthCoin { ))); } }, - EthCoinType::Nft { .. } => return MmError::err(ValidatePaymentError::NftProtocolNotSupported), + EthCoinType::Nft { .. } => { + return MmError::err(ValidatePaymentError::ProtocolNotSupported( + "Nft protocol is not supported by watchers yet".to_string(), + )) + }, } Ok(()) @@ -2165,13 +2443,10 @@ impl MarketCoinOps for EthCoin { let coin = self.clone(); let fut = async move { - let result = coin - .send_raw_transaction(bytes.into()) + coin.send_raw_transaction(bytes.into()) .await - .map(|res| format!("{:02x}", res)) - .map_err(|e| ERRL!("{}", e)); - - result + .map(|res| format!("{:02x}", res)) // TODO: add 0x hash (use unified hash format for eth wherever it is returned) + .map_err(|e| ERRL!("{}", e)) }; Box::new(fut.boxed().compat()) @@ -2286,18 +2561,18 @@ impl MarketCoinOps for EthCoin { Box::new(fut.boxed().compat()) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { - let unverified: UnverifiedTransactionWrapper = try_tx_fus!(rlp::decode(args.tx_bytes)); - let tx = try_tx_fus!(SignedEthTx::new(unverified)); + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { + let unverified: UnverifiedTransactionWrapper = try_tx_s!(rlp::decode(args.tx_bytes)); + let tx = try_tx_s!(SignedEthTx::new(unverified)); let swap_contract_address = match args.swap_contract_address { - Some(addr) => try_tx_fus!(addr.try_to_address()), + Some(addr) => try_tx_s!(addr.try_to_address()), None => match tx.unsigned().action() { Call(address) => *address, Create => { - return Box::new(futures01::future::err(TransactionErr::Plain(ERRL!( + return Err(TransactionErr::Plain(ERRL!( "Invalid payment action: the payment action cannot be create" - )))) + ))) }, }, }; @@ -2306,85 +2581,79 @@ impl MarketCoinOps for EthCoin { EthCoinType::Eth => get_function_name("ethPayment", args.watcher_reward), EthCoinType::Erc20 { .. } => get_function_name("erc20Payment", args.watcher_reward), EthCoinType::Nft { .. } => { - return Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( + return Err(TransactionErr::ProtocolNotSupported(ERRL!( "Nft Protocol is not supported yet!" - )))) + ))) }, }; - let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&func_name)); - let decoded = try_tx_fus!(decode_contract_call(payment_func, tx.unsigned().data())); + let payment_func = try_tx_s!(SWAP_CONTRACT.function(&func_name)); + let decoded = try_tx_s!(decode_contract_call(payment_func, tx.unsigned().data())); let id = match decoded.first() { Some(Token::FixedBytes(bytes)) => bytes.clone(), invalid_token => { - return Box::new(futures01::future::err(TransactionErr::Plain(ERRL!( + return Err(TransactionErr::Plain(ERRL!( "Expected Token::FixedBytes, got {:?}", invalid_token - )))) + ))) }, }; - let selfi = self.clone(); - let from_block = args.from_block; - let wait_until = args.wait_until; - let check_every = args.check_every; - let fut = async move { - loop { - if now_sec() > wait_until { - return TX_PLAIN_ERR!( - "Waited too long until {} for transaction {:?} to be spent ", - wait_until, - tx, - ); - } - let current_block = match selfi.current_block().compat().await { - Ok(b) => b, - Err(e) => { - error!("Error getting block number: {}", e); - Timer::sleep(5.).await; - continue; - }, - }; + loop { + if now_sec() > args.wait_until { + return TX_PLAIN_ERR!( + "Waited too long until {} for transaction {:?} to be spent ", + args.wait_until, + tx, + ); + } - let events = match selfi - .spend_events(swap_contract_address, from_block, current_block) - .compat() - .await - { - Ok(ev) => ev, - Err(e) => { - error!("Error getting spend events: {}", e); - Timer::sleep(5.).await; - continue; - }, - }; + let current_block = match self.current_block().compat().await { + Ok(b) => b, + Err(e) => { + error!("Error getting block number: {}", e); + Timer::sleep(5.).await; + continue; + }, + }; - let found = events.iter().find(|event| &event.data.0[..32] == id.as_slice()); + let events = match self + .spend_events(swap_contract_address, args.from_block, current_block) + .compat() + .await + { + Ok(ev) => ev, + Err(e) => { + error!("Error getting spend events: {}", e); + Timer::sleep(5.).await; + continue; + }, + }; - if let Some(event) = found { - if let Some(tx_hash) = event.transaction_hash { - let transaction = match selfi.transaction(TransactionId::Hash(tx_hash)).await { - Ok(Some(t)) => t, - Ok(None) => { - info!("Tx {} not found yet", tx_hash); - Timer::sleep(check_every).await; - continue; - }, - Err(e) => { - error!("Get tx {} error: {}", tx_hash, e); - Timer::sleep(check_every).await; - continue; - }, - }; + let found = events.iter().find(|event| &event.data.0[..32] == id.as_slice()); - return Ok(TransactionEnum::from(try_tx_s!(signed_tx_from_web3_tx(transaction)))); - } - } + if let Some(event) = found { + if let Some(tx_hash) = event.transaction_hash { + let transaction = match self.transaction(TransactionId::Hash(tx_hash)).await { + Ok(Some(t)) => t, + Ok(None) => { + info!("Tx {} not found yet", tx_hash); + Timer::sleep(args.check_every).await; + continue; + }, + Err(e) => { + error!("Get tx {} error: {}", tx_hash, e); + Timer::sleep(args.check_every).await; + continue; + }, + }; - Timer::sleep(5.).await; + return Ok(TransactionEnum::from(try_tx_s!(signed_tx_from_web3_tx(transaction)))); + } } - }; - Box::new(fut.boxed().compat()) + + Timer::sleep(5.).await; + } } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -2603,7 +2872,7 @@ async fn sign_raw_eth_tx(coin: &EthCoin, args: &SignEthTransactionParams) -> Raw let gas_price = coin.get_gas_price().await?; PayForGasOption::Legacy(LegacyGasPrice { gas_price }) }; - return sign_transaction_with_keypair( + sign_transaction_with_keypair( coin, key_pair, value, @@ -2617,7 +2886,7 @@ async fn sign_raw_eth_tx(coin: &EthCoin, args: &SignEthTransactionParams) -> Raw .map(|(signed_tx, _)| RawTransactionRes { tx_hex: signed_tx.tx_hex().into(), }) - .map_to_mm(|err| RawTransactionError::TransactionError(err.get_plain_text_format())); + .map_to_mm(|err| RawTransactionError::TransactionError(err.get_plain_text_format())) }, #[cfg(target_arch = "wasm32")] EthPrivKeyPolicy::Metamask(_) => MmError::err(RawTransactionError::InvalidParam( @@ -3554,7 +3823,7 @@ impl EthCoin { impl EthCoin { /// Sign and send eth transaction. /// This function is primarily for swap transactions so internally it relies on the swap tx fee policy - pub(crate) fn sign_and_send_transaction(&self, value: U256, action: Action, data: Vec, gas: U256) -> EthTxFut { + pub fn sign_and_send_transaction(&self, value: U256, action: Action, data: Vec, gas: U256) -> EthTxFut { let coin = self.clone(); let fut = async move { match coin.priv_key_policy { @@ -3586,7 +3855,7 @@ impl EthCoin { value, Action::Call(address), vec![], - U256::from(gas_limit::ETH_SEND_COINS), + U256::from(self.gas_limit.eth_send_coins), ), EthCoinType::Erc20 { platform: _, @@ -3599,14 +3868,12 @@ impl EthCoin { 0.into(), Action::Call(*token_addr), data, - U256::from(gas_limit::ETH_SEND_ERC20), + U256::from(self.gas_limit.eth_send_erc20), ) }, - EthCoinType::Nft { .. } => { - return Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( - "Nft Protocol is not supported yet!" - )))) - }, + EthCoinType::Nft { .. } => Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( + "Nft Protocol is not supported yet!" + )))), } } @@ -3654,7 +3921,7 @@ impl EthCoin { Token::Uint(time_lock), ])), }; - let gas = U256::from(gas_limit::ETH_PAYMENT); + let gas = U256::from(self.gas_limit.eth_payment); self.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas) }, EthCoinType::Erc20 { @@ -3725,7 +3992,7 @@ impl EthCoin { }; let wait_for_required_allowance_until = args.wait_for_confirmation_until; - let gas = U256::from(gas_limit::ERC20_PAYMENT); + let gas = U256::from(self.gas_limit.erc20_payment); let arc = self.clone(); Box::new(allowance_fut.and_then(move |allowed| -> EthTxFut { @@ -3767,11 +4034,9 @@ impl EthCoin { } })) }, - EthCoinType::Nft { .. } => { - return Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( - "Nft Protocol is not supported yet!" - )))) - }, + EthCoinType::Nft { .. } => Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( + "Nft Protocol is not supported yet!" + )))), } } @@ -3835,7 +4100,7 @@ impl EthCoin { 0.into(), Call(swap_contract_address), data, - U256::from(gas_limit::ETH_RECEIVER_SPEND), + U256::from(clone.gas_limit.eth_receiver_spend), ) }), ) @@ -3883,16 +4148,14 @@ impl EthCoin { 0.into(), Call(swap_contract_address), data, - U256::from(gas_limit::ERC20_RECEIVER_SPEND), + U256::from(clone.gas_limit.erc20_receiver_spend), ) }), ) }, - EthCoinType::Nft { .. } => { - return Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( - "Nft Protocol is not supported yet!" - )))) - }, + EthCoinType::Nft { .. } => Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( + "Nft Protocol is not supported yet!" + )))), } } @@ -3957,7 +4220,7 @@ impl EthCoin { 0.into(), Call(swap_contract_address), data, - U256::from(gas_limit::ETH_SENDER_REFUND), + U256::from(clone.gas_limit.eth_sender_refund), ) }), ) @@ -4008,16 +4271,14 @@ impl EthCoin { 0.into(), Call(swap_contract_address), data, - U256::from(gas_limit::ERC20_SENDER_REFUND), + U256::from(clone.gas_limit.erc20_sender_refund), ) }), ) }, - EthCoinType::Nft { .. } => { - return Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( - "Nft Protocol is not supported yet!" - )))) - }, + EthCoinType::Nft { .. } => Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( + "Nft Protocol is not supported yet!" + )))), } } @@ -4081,7 +4342,7 @@ impl EthCoin { 0.into(), Call(swap_contract_address), data, - U256::from(gas_limit::ETH_RECEIVER_SPEND), + U256::from(self.gas_limit.eth_receiver_spend), ) .compat() .await @@ -4133,16 +4394,14 @@ impl EthCoin { 0.into(), Call(swap_contract_address), data, - U256::from(gas_limit::ERC20_RECEIVER_SPEND), + U256::from(self.gas_limit.erc20_receiver_spend), ) .compat() .await }, - EthCoinType::Nft { .. } => { - return Err(TransactionErr::ProtocolNotSupported(ERRL!( - "Nft Protocol is not supported!" - ))) - }, + EthCoinType::Nft { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( + "Nft Protocol is not supported!" + ))), } } @@ -4206,7 +4465,7 @@ impl EthCoin { 0.into(), Call(swap_contract_address), data, - U256::from(gas_limit::ETH_SENDER_REFUND), + U256::from(self.gas_limit.eth_sender_refund), ) .compat() .await @@ -4258,16 +4517,14 @@ impl EthCoin { 0.into(), Call(swap_contract_address), data, - U256::from(gas_limit::ERC20_SENDER_REFUND), + U256::from(self.gas_limit.erc20_sender_refund), ) .compat() .await }, - EthCoinType::Nft { .. } => { - return Err(TransactionErr::ProtocolNotSupported(ERRL!( - "Nft Protocol is not supported yet!" - ))) - }, + EthCoinType::Nft { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( + "Nft Protocol is not supported yet!" + ))), } } @@ -4362,7 +4619,7 @@ impl EthCoin { self.get_token_balance_for_address(my_address, token_address).await } - async fn erc1155_balance(&self, token_addr: Address, token_id: &str) -> MmResult { + async fn erc1155_balance(&self, token_addr: Address, token_id: &str) -> MmResult { let wallet_amount_uint = match self.coin_type { EthCoinType::Eth | EthCoinType::Nft { .. } => { let function = ERC1155_CONTRACT.function("balanceOf")?; @@ -4388,7 +4645,8 @@ impl EthCoin { )) }, }; - let wallet_amount = u256_to_big_decimal(wallet_amount_uint, self.decimals)?; + // The "balanceOf" function in ERC1155 standard returns the exact count of tokens held by address without any decimals or scaling factors + let wallet_amount = wallet_amount_uint.to_string().parse::()?; Ok(wallet_amount) } @@ -4493,7 +4751,7 @@ impl EthCoin { self.call(request, Some(BlockId::Number(BlockNumber::Latest))).await } - fn allowance(&self, spender: Address) -> Web3RpcFut { + pub fn allowance(&self, spender: Address) -> Web3RpcFut { let coin = self.clone(); let fut = async move { match coin.coin_type { @@ -4558,7 +4816,7 @@ impl EthCoin { Box::new(fut.boxed().compat()) } - fn approve(&self, spender: Address, amount: U256) -> EthTxFut { + pub fn approve(&self, spender: Address, amount: U256) -> EthTxFut { let coin = self.clone(); let fut = async move { let token_addr = match coin.coin_type { @@ -4566,7 +4824,7 @@ impl EthCoin { EthCoinType::Erc20 { token_addr, .. } => token_addr, EthCoinType::Nft { .. } => { return Err(TransactionErr::ProtocolNotSupported(ERRL!( - "Nft Protocol is not supported yet!" + "Nft Protocol is not supported by 'approve'!" ))) }, }; @@ -4906,7 +5164,11 @@ impl EthCoin { ))); } }, - EthCoinType::Nft { .. } => return MmError::err(ValidatePaymentError::NftProtocolNotSupported), + EthCoinType::Nft { .. } => { + return MmError::err(ValidatePaymentError::ProtocolNotSupported( + "Nft protocol is not supported by legacy swap".to_string(), + )) + }, } Ok(()) @@ -5514,7 +5776,7 @@ impl MmCoin for EthCoin { .await .map_err(|e| e.to_string())?; - let fee = calc_total_fee(U256::from(gas_limit::ETH_MAX_TRADE_GAS), &pay_for_gas_option) + let fee = calc_total_fee(U256::from(coin.gas_limit.eth_max_trade_gas), &pay_for_gas_option) .map_err(|e| e.to_string())?; let fee_coin = match &coin.coin_type { EthCoinType::Eth => &coin.ticker, @@ -5546,13 +5808,13 @@ impl MmCoin for EthCoin { EthCoinType::Eth => { // this gas_limit includes gas for `ethPayment` and optionally `senderRefund` contract calls if include_refund_fee { - U256::from(gas_limit::ETH_PAYMENT) + U256::from(gas_limit::ETH_SENDER_REFUND) + U256::from(self.gas_limit.eth_payment) + U256::from(self.gas_limit.eth_sender_refund) } else { - U256::from(gas_limit::ETH_PAYMENT) + U256::from(self.gas_limit.eth_payment) } }, EthCoinType::Erc20 { token_addr, .. } => { - let mut gas = U256::from(gas_limit::ERC20_PAYMENT); + let mut gas = U256::from(self.gas_limit.erc20_payment); let value = match value { TradePreimageValue::Exact(value) | TradePreimageValue::UpperBound(value) => { wei_from_big_decimal(&value, self.decimals)? @@ -5573,8 +5835,9 @@ impl MmCoin for EthCoin { // this gas_limit includes gas for `approve`, `erc20Payment` contract calls gas += approve_gas_limit; } + // add 'senderRefund' gas if requested if include_refund_fee { - gas += U256::from(gas_limit::ERC20_SENDER_REFUND); // add 'senderRefund' gas if requested + gas += U256::from(self.gas_limit.erc20_sender_refund); } gas }, @@ -5605,11 +5868,11 @@ impl MmCoin for EthCoin { let (fee_coin, total_fee) = match &coin.coin_type { EthCoinType::Eth => ( &coin.ticker, - calc_total_fee(U256::from(gas_limit::ETH_RECEIVER_SPEND), &pay_for_gas_option)?, + calc_total_fee(U256::from(coin.gas_limit.eth_receiver_spend), &pay_for_gas_option)?, ), EthCoinType::Erc20 { platform, .. } => ( platform, - calc_total_fee(U256::from(gas_limit::ERC20_RECEIVER_SPEND), &pay_for_gas_option)?, + calc_total_fee(U256::from(coin.gas_limit.erc20_receiver_spend), &pay_for_gas_option)?, ), EthCoinType::Nft { .. } => return MmError::err(TradePreimageError::NftProtocolNotSupported), }; @@ -5748,44 +6011,6 @@ impl TryToAddress for Option { } } -pub trait GuiAuthMessages { - fn gui_auth_sign_message_hash(message: String) -> Option<[u8; 32]>; - fn generate_gui_auth_signed_validation(generator: GuiAuthValidationGenerator) - -> SignatureResult; -} - -impl GuiAuthMessages for EthCoin { - fn gui_auth_sign_message_hash(message: String) -> Option<[u8; 32]> { - let message_prefix = "atomicDEX Auth Ethereum Signed Message:\n"; - let prefix_len = CompactInteger::from(message_prefix.len()); - - let mut stream = Stream::new(); - prefix_len.serialize(&mut stream); - stream.append_slice(message_prefix.as_bytes()); - stream.append_slice(message.len().to_string().as_bytes()); - stream.append_slice(message.as_bytes()); - - Some(keccak256(&stream.out()).take()) - } - - fn generate_gui_auth_signed_validation( - generator: GuiAuthValidationGenerator, - ) -> SignatureResult { - let timestamp_message = get_utc_timestamp() + GUI_AUTH_SIGNED_MESSAGE_LIFETIME_SEC; - - let message_hash = - EthCoin::gui_auth_sign_message_hash(timestamp_message.to_string()).ok_or(SignatureError::PrefixNotFound)?; - let signature = sign(&generator.secret, &H256::from(message_hash))?; - - Ok(GuiAuthValidation { - coin_ticker: generator.coin_ticker, - address: generator.address, - timestamp_message, - signature: format!("0x{}", signature), - }) - } -} - fn validate_fee_impl(coin: EthCoin, validate_fee_args: EthValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { let fee_tx_hash = validate_fee_args.fee_tx_hash.to_owned(); let sender_addr = try_f!( @@ -5881,7 +6106,11 @@ fn validate_fee_impl(coin: EthCoin, validate_fee_args: EthValidateFeeArgs<'_>) - }, } }, - EthCoinType::Nft { .. } => return MmError::err(ValidatePaymentError::NftProtocolNotSupported), + EthCoinType::Nft { .. } => { + return MmError::err(ValidatePaymentError::ProtocolNotSupported( + "Nft protocol is not supported".to_string(), + )) + }, } Ok(()) @@ -5954,6 +6183,12 @@ pub fn wei_from_big_decimal(amount: &BigDecimal, decimals: u8) -> NumConversResu U256::from_dec_str(&amount).map_to_mm(|e| NumConversError::new(format!("{:?}", e))) } +pub fn wei_from_gwei_decimal(bigdec: &BigDecimal) -> NumConversResult { + wei_from_big_decimal(bigdec, ETH_GWEI_DECIMALS) +} + +pub fn wei_to_gwei_decimal(wei: U256) -> NumConversResult { u256_to_big_decimal(wei, ETH_GWEI_DECIMALS) } + impl Transaction for SignedEthTx { fn tx_hex(&self) -> Vec { rlp::encode(self).to_vec() } @@ -6081,32 +6316,6 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result, token_addr: Address) -> Result { - let function = try_s!(ERC20_CONTRACT.function("decimals")); - let data = try_s!(function.encode_input(&[])); - let request = CallRequest { - from: Some(Address::default()), - to: Some(token_addr), - gas: None, - gas_price: None, - value: Some(0.into()), - data: Some(data.into()), - ..CallRequest::default() - }; - - let res = web3 - .eth() - .call(request, Some(BlockId::Number(BlockNumber::Latest))) - .map_err(|e| ERRL!("{}", e)) - .await?; - let tokens = try_s!(function.decode_output(&res.0)); - let decimals = match tokens[0] { - Token::Uint(dec) => dec.as_u64(), - _ => return ERR!("Invalid decimals type {:?}", tokens), - }; - Ok(decimals as u8) -} - pub fn valid_addr_from_str(addr_str: &str) -> Result { let addr = try_s!(addr_from_str(addr_str)); if !is_valid_checksum_addr(addr_str) { @@ -6239,10 +6448,7 @@ pub async fn eth_coin_from_conf_and_request( Some("ws") | Some("wss") => { const TMP_SOCKET_CONNECTION: Duration = Duration::from_secs(20); - let node = WebsocketTransportNode { - uri: uri.clone(), - gui_auth: false, - }; + let node = WebsocketTransportNode { uri: uri.clone() }; let websocket_transport = WebsocketTransport::with_event_handlers(node, event_handlers.clone()); // Temporarily start the connection loop (we close the connection once we have the client version below). @@ -6257,7 +6463,10 @@ pub async fn eth_coin_from_conf_and_request( Web3Transport::Websocket(websocket_transport) }, Some("http") | Some("https") => { - let node = HttpTransportNode { uri, gui_auth: false }; + let node = HttpTransportNode { + uri, + komodo_proxy: false, + }; Web3Transport::new_http_with_event_handlers(node, event_handlers.clone()) }, @@ -6361,6 +6570,8 @@ pub async fn eth_coin_from_conf_and_request( let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(ctx, conf, &coin_type).await?; let max_eth_tx_type = get_max_eth_tx_type_conf(ctx, conf, &coin_type).await?; + let gas_limit: EthGasLimit = extract_gas_limit_from_conf(conf)?; + let gas_limit_v2: EthGasLimitV2 = extract_gas_limit_from_conf(conf)?; let coin = EthCoinImpl { priv_key_policy: key_pair, @@ -6368,6 +6579,7 @@ pub async fn eth_coin_from_conf_and_request( coin_type, sign_message_prefix, swap_contract_address, + swap_v2_contracts: None, fallback_swap_contract, contract_supports_watchers, decimals, @@ -6385,6 +6597,8 @@ pub async fn eth_coin_from_conf_and_request( erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), platform_fee_estimator_state, + gas_limit, + gas_limit_v2, abortable_system, }; @@ -6642,7 +6856,7 @@ async fn get_eth_gas_details_from_withdraw_fee( // covering edge case by deducting the standard transfer fee when we want to max withdraw ETH let eth_value_for_estimate = if fungible_max && eth_coin.coin_type == EthCoinType::Eth { - eth_value - calc_total_fee(U256::from(gas_limit::ETH_SEND_COINS), &pay_for_gas_option)? + eth_value - calc_total_fee(U256::from(eth_coin.gas_limit.eth_send_coins), &pay_for_gas_option)? } else { eth_value }; @@ -6747,20 +6961,12 @@ impl ToBytes for SignedEthTx { } } -#[derive(Debug, Display)] +#[derive(Debug, Display, EnumFromStringify)] pub enum EthAssocTypesError { InvalidHexString(String), + #[from_stringify("DecoderError")] TxParseError(String), ParseSignatureError(String), - KeysError(keys::Error), -} - -impl From for EthAssocTypesError { - fn from(e: DecoderError) -> Self { EthAssocTypesError::TxParseError(e.to_string()) } -} - -impl From for EthAssocTypesError { - fn from(e: keys::Error) -> Self { EthAssocTypesError::KeysError(e) } } #[derive(Debug, Display)] @@ -6778,7 +6984,7 @@ impl From for EthNftAssocTypesError { impl ParseCoinAssocTypes for EthCoin { type Address = Address; type AddressParseError = MmError; - type Pubkey = HtlcPubKey; + type Pubkey = Public; type PubkeyParseError = MmError; type Tx = SignedEthTx; type TxParseError = MmError; @@ -6803,8 +7009,9 @@ impl ParseCoinAssocTypes for EthCoin { Address::from_str(address).map_to_mm(|e| EthAssocTypesError::InvalidHexString(e.to_string())) } + /// As derive_htlc_pubkey_v2 returns coin specific pubkey we can use [Public::from_slice] directly fn parse_pubkey(&self, pubkey: &[u8]) -> Result { - HtlcPubKey::from_slice(pubkey).map_to_mm(EthAssocTypesError::from) + Ok(Public::from_slice(pubkey)) } fn parse_tx(&self, tx: &[u8]) -> Result { @@ -6839,6 +7046,10 @@ impl ToBytes for ContractType { fn to_bytes(&self) -> Vec { self.to_string().into_bytes() } } +impl ToBytes for Public { + fn to_bytes(&self) -> Vec { self.0.to_vec() } +} + impl ParseNftAssocTypes for EthCoin { type ContractAddress = Address; type TokenId = BigUint; @@ -6889,16 +7100,16 @@ impl MakerNftSwapOpsV2 for EthCoin { async fn refund_nft_maker_payment_v2_timelock( &self, - args: RefundPaymentArgs<'_>, + args: RefundNftMakerPaymentArgs<'_, Self>, ) -> Result { self.refund_nft_maker_payment_v2_timelock_impl(args).await } async fn refund_nft_maker_payment_v2_secret( &self, - _args: RefundMakerPaymentArgs<'_, Self>, + args: RefundNftMakerPaymentArgs<'_, Self>, ) -> Result { - todo!() + self.refund_nft_maker_payment_v2_secret_impl(args).await } } @@ -7013,6 +7224,15 @@ pub fn pubkey_from_extended(extended_pubkey: &Secp256k1ExtendedPublicKey) -> Pub pubkey_uncompressed } +fn extract_gas_limit_from_conf(coin_conf: &Json) -> Result { + let key = T::key(); + if coin_conf[key].is_null() { + Ok(Default::default()) + } else { + json::from_value(coin_conf[key].clone()).map_err(|e| e.to_string()) + } +} + impl Eip1559Ops for EthCoin { fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { self.swap_txfee_policy.lock().unwrap().clone() } @@ -7020,3 +7240,214 @@ impl Eip1559Ops for EthCoin { *self.swap_txfee_policy.lock().unwrap() = swap_txfee_policy } } + +#[async_trait] +impl TakerCoinSwapOpsV2 for EthCoin { + /// Wrapper for [EthCoin::send_taker_funding_impl] + async fn send_taker_funding(&self, args: SendTakerFundingArgs<'_>) -> Result { + self.send_taker_funding_impl(args).await + } + + /// Wrapper for [EthCoin::validate_taker_funding_impl] + async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateSwapV2TxResult { + self.validate_taker_funding_impl(args).await + } + + async fn refund_taker_funding_timelock( + &self, + args: RefundTakerPaymentArgs<'_>, + ) -> Result { + self.refund_taker_payment_with_timelock_impl(args).await + } + + async fn refund_taker_funding_secret( + &self, + args: RefundFundingSecretArgs<'_, Self>, + ) -> Result { + self.refund_taker_funding_secret_impl(args).await + } + + /// Wrapper for [EthCoin::search_for_taker_funding_spend_impl] + async fn search_for_taker_funding_spend( + &self, + tx: &Self::Tx, + _from_block: u64, + _secret_hash: &[u8], + ) -> Result>, SearchForFundingSpendErr> { + self.search_for_taker_funding_spend_impl(tx).await + } + + /// Eth doesnt have preimages + async fn gen_taker_funding_spend_preimage( + &self, + args: &GenTakerFundingSpendArgs<'_, Self>, + _swap_unique_data: &[u8], + ) -> GenPreimageResult { + Ok(TxPreimageWithSig { + preimage: args.funding_tx.clone(), + signature: args.funding_tx.signature(), + }) + } + + /// Eth doesnt have preimages + async fn validate_taker_funding_spend_preimage( + &self, + _gen_args: &GenTakerFundingSpendArgs<'_, Self>, + _preimage: &TxPreimageWithSig, + ) -> ValidateTakerFundingSpendPreimageResult { + Ok(()) + } + + /// Wrapper for [EthCoin::taker_payment_approve] + async fn sign_and_send_taker_funding_spend( + &self, + _preimage: &TxPreimageWithSig, + args: &GenTakerFundingSpendArgs<'_, Self>, + _swap_unique_data: &[u8], + ) -> Result { + self.taker_payment_approve(args).await + } + + async fn refund_combined_taker_payment( + &self, + args: RefundTakerPaymentArgs<'_>, + ) -> Result { + self.refund_taker_payment_with_timelock_impl(args).await + } + + /// Eth doesnt have preimages + async fn gen_taker_payment_spend_preimage( + &self, + args: &GenTakerPaymentSpendArgs<'_, Self>, + _swap_unique_data: &[u8], + ) -> GenPreimageResult { + Ok(TxPreimageWithSig { + preimage: args.taker_tx.clone(), + signature: args.taker_tx.signature(), + }) + } + + /// Eth doesnt have preimages + async fn validate_taker_payment_spend_preimage( + &self, + _gen_args: &GenTakerPaymentSpendArgs<'_, Self>, + _preimage: &TxPreimageWithSig, + ) -> ValidateTakerPaymentSpendPreimageResult { + Ok(()) + } + + /// Wrapper for [EthCoin::sign_and_broadcast_taker_payment_spend_impl] + async fn sign_and_broadcast_taker_payment_spend( + &self, + _preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, + secret: &[u8], + _swap_unique_data: &[u8], + ) -> Result { + self.sign_and_broadcast_taker_payment_spend_impl(gen_args, secret).await + } + + /// Wrapper for [EthCoin::wait_for_taker_payment_spend_impl] + async fn wait_for_taker_payment_spend( + &self, + taker_payment: &Self::Tx, + _from_block: u64, + wait_until: u64, + ) -> MmResult { + self.wait_for_taker_payment_spend_impl(taker_payment, wait_until).await + } +} + +impl CommonSwapOpsV2 for EthCoin { + #[inline(always)] + fn derive_htlc_pubkey_v2(&self, _swap_unique_data: &[u8]) -> Self::Pubkey { + match self.priv_key_policy { + EthPrivKeyPolicy::Iguana(ref key_pair) + | EthPrivKeyPolicy::HDWallet { + activated_key: ref key_pair, + .. + } => *key_pair.public(), + EthPrivKeyPolicy::Trezor => todo!(), + #[cfg(target_arch = "wasm32")] + EthPrivKeyPolicy::Metamask(ref metamask_policy) => { + // The metamask public key should be uncompressed + // Remove the first byte (0x04) from the uncompressed public key + let pubkey_bytes: [u8; 64] = metamask_policy.public_key_uncompressed[1..65] + .try_into() + .expect("slice with incorrect length"); + Public::from_slice(&pubkey_bytes) + }, + } + } + + #[inline(always)] + fn derive_htlc_pubkey_v2_bytes(&self, swap_unique_data: &[u8]) -> Vec { + self.derive_htlc_pubkey_v2(swap_unique_data).to_bytes() + } +} + +#[cfg(all(feature = "for-tests", not(target_arch = "wasm32")))] +impl EthCoin { + pub async fn set_coin_type(&self, new_coin_type: EthCoinType) -> EthCoin { + let coin = EthCoinImpl { + ticker: self.ticker.clone(), + coin_type: new_coin_type, + priv_key_policy: self.priv_key_policy.clone(), + derivation_method: Arc::clone(&self.derivation_method), + sign_message_prefix: self.sign_message_prefix.clone(), + swap_contract_address: self.swap_contract_address, + swap_v2_contracts: self.swap_v2_contracts, + fallback_swap_contract: self.fallback_swap_contract, + contract_supports_watchers: self.contract_supports_watchers, + web3_instances: AsyncMutex::new(self.web3_instances.lock().await.clone()), + decimals: self.decimals, + history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()), + required_confirmations: AtomicU64::new( + self.required_confirmations.load(std::sync::atomic::Ordering::SeqCst), + ), + swap_txfee_policy: Mutex::new(self.swap_txfee_policy.lock().unwrap().clone()), + max_eth_tx_type: self.max_eth_tx_type, + ctx: self.ctx.clone(), + chain_id: self.chain_id, + trezor_coin: self.trezor_coin.clone(), + logs_block_range: self.logs_block_range, + address_nonce_locks: Arc::clone(&self.address_nonce_locks), + erc20_tokens_infos: Arc::clone(&self.erc20_tokens_infos), + nfts_infos: Arc::clone(&self.nfts_infos), + platform_fee_estimator_state: Arc::clone(&self.platform_fee_estimator_state), + gas_limit: EthGasLimit::default(), + gas_limit_v2: EthGasLimitV2::default(), + abortable_system: self.abortable_system.create_subsystem().unwrap(), + }; + EthCoin(Arc::new(coin)) + } +} + +#[async_trait] +impl MakerCoinSwapOpsV2 for EthCoin { + async fn send_maker_payment_v2(&self, args: SendMakerPaymentArgs<'_, Self>) -> Result { + self.send_maker_payment_v2_impl(args).await + } + + async fn validate_maker_payment_v2(&self, args: ValidateMakerPaymentArgs<'_, Self>) -> ValidatePaymentResult<()> { + self.validate_maker_payment_v2_impl(args).await + } + + async fn refund_maker_payment_v2_timelock( + &self, + args: RefundMakerPaymentTimelockArgs<'_>, + ) -> Result { + self.refund_maker_payment_v2_timelock_impl(args).await + } + + async fn refund_maker_payment_v2_secret( + &self, + args: RefundMakerPaymentSecretArgs<'_, Self>, + ) -> Result { + self.refund_maker_payment_v2_secret_impl(args).await + } + + async fn spend_maker_payment_v2(&self, args: SpendMakerPaymentArgs<'_, Self>) -> Result { + self.spend_maker_payment_v2_impl(args).await + } +} diff --git a/mm2src/coins/eth/eip1559_gas_fee.rs b/mm2src/coins/eth/eip1559_gas_fee.rs index 4d33781f39..32ac186169 100644 --- a/mm2src/coins/eth/eip1559_gas_fee.rs +++ b/mm2src/coins/eth/eip1559_gas_fee.rs @@ -1,8 +1,8 @@ //! Provides estimations of base and priority fee per gas or fetch estimations from a gas api provider use super::web3_transport::FeeHistoryResult; -use super::{Web3RpcError, Web3RpcResult}; -use crate::{wei_from_gwei_decimal, wei_to_gwei_decimal, EthCoin, NumConversError}; +use super::{wei_from_gwei_decimal, wei_to_gwei_decimal, Web3RpcError, Web3RpcResult}; +use crate::{EthCoin, NumConversError}; use ethereum_types::U256; use mm2_err_handle::mm_error::MmError; use mm2_err_handle::or_mm_error::OrMmError; @@ -104,24 +104,24 @@ impl TryFrom for FeePerGasEstimated { fn try_from(infura_fees: InfuraFeePerGas) -> Result { Ok(Self { - base_fee: wei_from_gwei_decimal!(&infura_fees.estimated_base_fee)?, + base_fee: wei_from_gwei_decimal(&infura_fees.estimated_base_fee)?, low: FeePerGasLevel { - max_fee_per_gas: wei_from_gwei_decimal!(&infura_fees.low.suggested_max_fee_per_gas)?, - max_priority_fee_per_gas: wei_from_gwei_decimal!(&infura_fees.low.suggested_max_priority_fee_per_gas)?, + max_fee_per_gas: wei_from_gwei_decimal(&infura_fees.low.suggested_max_fee_per_gas)?, + max_priority_fee_per_gas: wei_from_gwei_decimal(&infura_fees.low.suggested_max_priority_fee_per_gas)?, min_wait_time: Some(infura_fees.low.min_wait_time_estimate), max_wait_time: Some(infura_fees.low.max_wait_time_estimate), }, medium: FeePerGasLevel { - max_fee_per_gas: wei_from_gwei_decimal!(&infura_fees.medium.suggested_max_fee_per_gas)?, - max_priority_fee_per_gas: wei_from_gwei_decimal!( - &infura_fees.medium.suggested_max_priority_fee_per_gas + max_fee_per_gas: wei_from_gwei_decimal(&infura_fees.medium.suggested_max_fee_per_gas)?, + max_priority_fee_per_gas: wei_from_gwei_decimal( + &infura_fees.medium.suggested_max_priority_fee_per_gas, )?, min_wait_time: Some(infura_fees.medium.min_wait_time_estimate), max_wait_time: Some(infura_fees.medium.max_wait_time_estimate), }, high: FeePerGasLevel { - max_fee_per_gas: wei_from_gwei_decimal!(&infura_fees.high.suggested_max_fee_per_gas)?, - max_priority_fee_per_gas: wei_from_gwei_decimal!(&infura_fees.high.suggested_max_priority_fee_per_gas)?, + max_fee_per_gas: wei_from_gwei_decimal(&infura_fees.high.suggested_max_fee_per_gas)?, + max_priority_fee_per_gas: wei_from_gwei_decimal(&infura_fees.high.suggested_max_priority_fee_per_gas)?, min_wait_time: Some(infura_fees.high.min_wait_time_estimate), max_wait_time: Some(infura_fees.high.max_wait_time_estimate), }, @@ -143,33 +143,33 @@ impl TryFrom for FeePerGasEstimated { return Ok(FeePerGasEstimated::default()); } Ok(Self { - base_fee: wei_from_gwei_decimal!(&block_prices.block_prices[0].base_fee_per_gas)?, + base_fee: wei_from_gwei_decimal(&block_prices.block_prices[0].base_fee_per_gas)?, low: FeePerGasLevel { - max_fee_per_gas: wei_from_gwei_decimal!( - &block_prices.block_prices[0].estimated_prices[2].max_fee_per_gas + max_fee_per_gas: wei_from_gwei_decimal( + &block_prices.block_prices[0].estimated_prices[2].max_fee_per_gas, )?, - max_priority_fee_per_gas: wei_from_gwei_decimal!( - &block_prices.block_prices[0].estimated_prices[2].max_priority_fee_per_gas + max_priority_fee_per_gas: wei_from_gwei_decimal( + &block_prices.block_prices[0].estimated_prices[2].max_priority_fee_per_gas, )?, min_wait_time: None, max_wait_time: None, }, medium: FeePerGasLevel { - max_fee_per_gas: wei_from_gwei_decimal!( - &block_prices.block_prices[0].estimated_prices[1].max_fee_per_gas + max_fee_per_gas: wei_from_gwei_decimal( + &block_prices.block_prices[0].estimated_prices[1].max_fee_per_gas, )?, - max_priority_fee_per_gas: wei_from_gwei_decimal!( - &block_prices.block_prices[0].estimated_prices[1].max_priority_fee_per_gas + max_priority_fee_per_gas: wei_from_gwei_decimal( + &block_prices.block_prices[0].estimated_prices[1].max_priority_fee_per_gas, )?, min_wait_time: None, max_wait_time: None, }, high: FeePerGasLevel { - max_fee_per_gas: wei_from_gwei_decimal!( - &block_prices.block_prices[0].estimated_prices[0].max_fee_per_gas + max_fee_per_gas: wei_from_gwei_decimal( + &block_prices.block_prices[0].estimated_prices[0].max_fee_per_gas, )?, - max_priority_fee_per_gas: wei_from_gwei_decimal!( - &block_prices.block_prices[0].estimated_prices[0].max_priority_fee_per_gas + max_priority_fee_per_gas: wei_from_gwei_decimal( + &block_prices.block_prices[0].estimated_prices[0].max_priority_fee_per_gas, )?, min_wait_time: None, max_wait_time: None, @@ -260,7 +260,7 @@ impl FeePerGasSimpleEstimator { let max_priority_fee_per_gas = Self::percentile_of(&level_rewards, Self::PRIORITY_FEE_PERCENTILES[level_index]); // Convert the priority fee to BigDecimal gwei, falling back to 0 on error. let max_priority_fee_per_gas_gwei = - wei_to_gwei_decimal!(max_priority_fee_per_gas).unwrap_or_else(|_| BigDecimal::from(0)); + wei_to_gwei_decimal(max_priority_fee_per_gas).unwrap_or_else(|_| BigDecimal::from(0)); // Calculate the max fee per gas by adjusting the base fee and adding the priority fee. let adjust_max_fee = @@ -273,7 +273,7 @@ impl FeePerGasSimpleEstimator { Ok(FeePerGasLevel { max_priority_fee_per_gas, - max_fee_per_gas: wei_from_gwei_decimal!(&max_fee_per_gas_dec)?, + max_fee_per_gas: wei_from_gwei_decimal(&max_fee_per_gas_dec)?, // TODO: Consider adding default wait times if applicable (and mark them as uncertain). min_wait_time: None, max_wait_time: None, @@ -290,7 +290,7 @@ impl FeePerGasSimpleEstimator { .first() .cloned() .unwrap_or_else(|| U256::from(0)); - let latest_base_fee_dec = wei_to_gwei_decimal!(latest_base_fee).unwrap_or_else(|_| BigDecimal::from(0)); + let latest_base_fee_dec = wei_to_gwei_decimal(latest_base_fee).unwrap_or_else(|_| BigDecimal::from(0)); // The predicted base fee is not used for calculating eip1559 values here and is provided for other purposes // (f.e if the caller would like to do own estimates of max fee and max priority fee) diff --git a/mm2src/coins/eth/erc20.rs b/mm2src/coins/eth/erc20.rs new file mode 100644 index 0000000000..75f7033fda --- /dev/null +++ b/mm2src/coins/eth/erc20.rs @@ -0,0 +1,107 @@ +use crate::eth::web3_transport::Web3Transport; +use crate::eth::{EthCoin, ERC20_CONTRACT}; +use crate::{CoinsContext, MmCoinEnum}; +use ethabi::Token; +use ethereum_types::Address; +use futures_util::TryFutureExt; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::mm_error::MmResult; +use web3::types::{BlockId, BlockNumber, CallRequest}; +use web3::{Transport, Web3}; + +async fn call_erc20_function( + web3: &Web3, + token_addr: Address, + function_name: &str, +) -> Result, String> { + let function = try_s!(ERC20_CONTRACT.function(function_name)); + let data = try_s!(function.encode_input(&[])); + let request = CallRequest { + from: Some(Address::default()), + to: Some(token_addr), + gas: None, + gas_price: None, + value: Some(0.into()), + data: Some(data.into()), + ..CallRequest::default() + }; + + let res = web3 + .eth() + .call(request, Some(BlockId::Number(BlockNumber::Latest))) + .map_err(|e| ERRL!("{}", e)) + .await?; + function.decode_output(&res.0).map_err(|e| ERRL!("{}", e)) +} + +pub(crate) async fn get_token_decimals(web3: &Web3, token_addr: Address) -> Result { + let tokens = call_erc20_function(web3, token_addr, "decimals").await?; + let Some(token) = tokens.into_iter().next() else { + return ERR!("No value returned from decimals() call"); + }; + let Token::Uint(dec) = token else { + return ERR!("Expected Uint token for decimals, got {:?}", token); + }; + Ok(dec.as_u64() as u8) +} + +async fn get_token_symbol(coin: &EthCoin, token_addr: Address) -> Result { + let web3 = try_s!(coin.web3().await); + let tokens = call_erc20_function(&web3, token_addr, "symbol").await?; + let Some(token) = tokens.into_iter().next() else { + return ERR!("No value returned from symbol() call"); + }; + let Token::String(symbol) = token else { + return ERR!("Expected String token for symbol, got {:?}", token); + }; + Ok(symbol) +} + +#[derive(Serialize)] +pub struct Erc20TokenInfo { + pub symbol: String, + pub decimals: u8, +} + +pub async fn get_erc20_token_info(coin: &EthCoin, token_addr: Address) -> Result { + let symbol = get_token_symbol(coin, token_addr).await?; + let web3 = try_s!(coin.web3().await); + let decimals = get_token_decimals(&web3, token_addr).await?; + Ok(Erc20TokenInfo { symbol, decimals }) +} + +/// Finds if an ERC20 token is in coins config by its contract address and returns its ticker. +pub fn get_erc20_ticker_by_contract_address(ctx: &MmArc, platform: &str, contract_address: &str) -> Option { + ctx.conf["coins"].as_array()?.iter().find_map(|coin| { + let protocol = coin.get("protocol")?; + let protocol_type = protocol.get("type")?.as_str()?; + if protocol_type != "ERC20" { + return None; + } + let protocol_data = protocol.get("protocol_data")?; + let coin_platform = protocol_data.get("platform")?.as_str()?; + let coin_contract_address = protocol_data.get("contract_address")?.as_str()?; + + if coin_platform == platform && coin_contract_address == contract_address { + coin.get("coin")?.as_str().map(|s| s.to_string()) + } else { + None + } + }) +} + +/// Finds an enabled ERC20 token by its contract address and returns it as `MmCoinEnum`. +pub async fn get_enabled_erc20_by_contract( + ctx: &MmArc, + contract_address: Address, +) -> MmResult, String> { + let cctx = CoinsContext::from_ctx(ctx)?; + let coins = cctx.coins.lock().await; + + Ok(coins.values().find_map(|coin| match &coin.inner { + MmCoinEnum::EthCoin(eth_coin) if eth_coin.erc20_token_address() == Some(contract_address) => { + Some(coin.inner.clone()) + }, + _ => None, + })) +} diff --git a/mm2src/coins/eth/eth_balance_events.rs b/mm2src/coins/eth/eth_balance_events.rs index 231aa68507..0cc798ad7e 100644 --- a/mm2src/coins/eth/eth_balance_events.rs +++ b/mm2src/coins/eth/eth_balance_events.rs @@ -14,7 +14,7 @@ use mm2_number::BigDecimal; use std::collections::{HashMap, HashSet}; use super::EthCoin; -use crate::{eth::{u256_to_big_decimal, Erc20TokenInfo}, +use crate::{eth::{u256_to_big_decimal, Erc20TokenDetails}, BalanceError, CoinWithDerivationMethod, MmCoin}; struct BalanceData { @@ -40,9 +40,9 @@ async fn get_all_balance_results_concurrently(coin: &EthCoin, addresses: HashSet // // Unlike tokens, the platform coin length is constant (=1). Instead of creating a generic // type and mapping the platform coin and the entire token list (which can grow at any time), we map - // the platform coin to Erc20TokenInfo so that we can use the token list right away without + // the platform coin to Erc20TokenDetails so that we can use the token list right away without // additional mapping. - tokens.insert(coin.ticker.clone(), Erc20TokenInfo { + tokens.insert(coin.ticker.clone(), Erc20TokenDetails { // This is a dummy value, since there is no token address for the platform coin. // In the fetch_balance function, we check if the token_ticker is equal to this // coin's ticker to avoid using token_address to fetch the balance @@ -72,7 +72,7 @@ async fn fetch_balance( coin: &EthCoin, address: Address, token_ticker: String, - info: &Erc20TokenInfo, + info: &Erc20TokenDetails, ) -> Result { let (balance_as_u256, decimals) = if token_ticker == coin.ticker { ( diff --git a/mm2src/coins/eth/eth_hd_wallet.rs b/mm2src/coins/eth/eth_hd_wallet.rs index 60e3942dc6..ceff4ccb79 100644 --- a/mm2src/coins/eth/eth_hd_wallet.rs +++ b/mm2src/coins/eth/eth_hd_wallet.rs @@ -8,7 +8,7 @@ use crypto::Secp256k1ExtendedPublicKey; use ethereum_types::{Address, Public}; pub type EthHDAddress = HDAddress; -pub type EthHDAccount = HDAccount; +pub type EthHDAccount = HDAccount; pub type EthHDWallet = HDWallet; #[async_trait] @@ -31,13 +31,13 @@ impl ExtractExtendedPubkey for EthCoin { impl HDWalletCoinOps for EthCoin { type HDWallet = EthHDWallet; - fn address_formatter(&self) -> fn(&HDCoinAddress) -> String { display_eth_address } + fn address_formatter(&self) -> fn(&Address) -> String { display_eth_address } fn address_from_extended_pubkey( &self, extended_pubkey: &Secp256k1ExtendedPublicKey, derivation_path: DerivationPath, - ) -> HDCoinHDAddress { + ) -> EthHDAddress { let pubkey = pubkey_from_extended(extended_pubkey); let address = public_to_address(&pubkey); EthHDAddress { @@ -109,7 +109,7 @@ impl HDWalletBalanceOps for EthCoin { async fn scan_for_new_addresses( &self, hd_wallet: &Self::HDWallet, - hd_account: &mut HDCoinHDAccount, + hd_account: &mut EthHDAccount, address_scanner: &Self::HDAddressScanner, gap_limit: u32, ) -> BalanceResult>> { @@ -126,7 +126,7 @@ impl HDWalletBalanceOps for EthCoin { async fn all_known_addresses_balances( &self, - hd_account: &HDCoinHDAccount, + hd_account: &EthHDAccount, ) -> BalanceResult>> { let external_addresses = hd_account .known_addresses_number(Bip44Chain::External) @@ -137,7 +137,7 @@ impl HDWalletBalanceOps for EthCoin { .await } - async fn known_address_balance(&self, address: &HDBalanceAddress) -> BalanceResult { + async fn known_address_balance(&self, address: &Address) -> BalanceResult { let balance = self .address_balance(*address) .and_then(move |result| Ok(u256_to_big_decimal(result, self.decimals())?)) @@ -158,8 +158,8 @@ impl HDWalletBalanceOps for EthCoin { async fn known_addresses_balances( &self, - addresses: Vec>, - ) -> BalanceResult, Self::BalanceObject)>> { + addresses: Vec
, + ) -> BalanceResult> { let mut balance_futs = Vec::new(); for address in addresses { let fut = async move { diff --git a/mm2src/coins/eth/eth_rpc.rs b/mm2src/coins/eth/eth_rpc.rs index 922e219fbd..3dc6711126 100644 --- a/mm2src/coins/eth/eth_rpc.rs +++ b/mm2src/coins/eth/eth_rpc.rs @@ -241,8 +241,8 @@ impl EthCoin { .and_then(|t| serde_json::from_value(t).map_err(Into::into)) } - /// Get chain id - pub(crate) async fn chain_id(&self) -> Result { + /// Get chain id from network + pub(crate) async fn network_chain_id(&self) -> Result { self.try_rpc_send("eth_chainId", vec![]) .await .and_then(|t| serde_json::from_value(t).map_err(Into::into)) diff --git a/mm2src/coins/eth/eth_swap_v2/eth_maker_swap_v2.rs b/mm2src/coins/eth/eth_swap_v2/eth_maker_swap_v2.rs new file mode 100644 index 0000000000..3089604ede --- /dev/null +++ b/mm2src/coins/eth/eth_swap_v2/eth_maker_swap_v2.rs @@ -0,0 +1,486 @@ +use super::{validate_amount, validate_from_to_and_status, EthPaymentType, PaymentMethod, PrepareTxDataError, + ZERO_VALUE}; +use crate::coin_errors::{ValidatePaymentError, ValidatePaymentResult}; +use crate::eth::{decode_contract_call, get_function_input_data, wei_from_big_decimal, EthCoin, EthCoinType, + MakerPaymentStateV2, SignedEthTx, MAKER_SWAP_V2}; +use crate::{ParseCoinAssocTypes, RefundMakerPaymentSecretArgs, RefundMakerPaymentTimelockArgs, SendMakerPaymentArgs, + SpendMakerPaymentArgs, SwapTxTypeWithSecretHash, TransactionErr, ValidateMakerPaymentArgs}; +use ethabi::{Function, Token}; +use ethcore_transaction::Action; +use ethereum_types::{Address, Public, U256}; +use ethkey::public_to_address; +use futures::compat::Future01CompatExt; +use mm2_err_handle::mm_error::MmError; +use mm2_err_handle::prelude::MapToMmResult; +use std::convert::TryInto; +use web3::types::TransactionId; + +const ETH_MAKER_PAYMENT: &str = "ethMakerPayment"; +const ERC20_MAKER_PAYMENT: &str = "erc20MakerPayment"; + +/// state index for `MakerPayment` structure from `EtomicSwapMakerV2.sol` +/// +/// struct MakerPayment { +/// bytes20 paymentHash; +/// uint32 paymentLockTime; +/// MakerPaymentState state; +/// } +const MAKER_PAYMENT_STATE_INDEX: usize = 2; + +struct MakerPaymentArgs { + taker_address: Address, + taker_secret_hash: [u8; 32], + maker_secret_hash: [u8; 32], + payment_time_lock: u64, +} + +struct MakerValidationArgs<'a> { + swap_id: Vec, + amount: U256, + taker: Address, + taker_secret_hash: &'a [u8; 32], + maker_secret_hash: &'a [u8; 32], + payment_time_lock: u64, +} + +struct MakerRefundTimelockArgs { + payment_amount: U256, + taker_address: Address, + taker_secret_hash: [u8; 32], + maker_secret_hash: [u8; 32], + payment_time_lock: u64, + token_address: Address, +} + +struct MakerRefundSecretArgs { + payment_amount: U256, + taker_address: Address, + taker_secret: [u8; 32], + maker_secret_hash: [u8; 32], + payment_time_lock: u64, + token_address: Address, +} + +impl EthCoin { + pub(crate) async fn send_maker_payment_v2_impl( + &self, + args: SendMakerPaymentArgs<'_, Self>, + ) -> Result { + let maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))? + .maker_swap_v2_contract; + let payment_amount = try_tx_s!(wei_from_big_decimal(&args.amount, self.decimals)); + let payment_args = { + let taker_address = public_to_address(args.taker_pub); + MakerPaymentArgs { + taker_address, + taker_secret_hash: try_tx_s!(args.taker_secret_hash.try_into()), + maker_secret_hash: try_tx_s!(args.maker_secret_hash.try_into()), + payment_time_lock: args.time_lock, + } + }; + match &self.coin_type { + EthCoinType::Eth => { + let data = try_tx_s!(self.prepare_maker_eth_payment_data(&payment_args).await); + self.sign_and_send_transaction( + payment_amount, + Action::Call(maker_swap_v2_contract), + data, + U256::from(self.gas_limit_v2.maker.eth_payment), + ) + .compat() + .await + }, + EthCoinType::Erc20 { + platform: _, + token_addr, + } => { + let data = try_tx_s!( + self.prepare_maker_erc20_payment_data(&payment_args, payment_amount, *token_addr) + .await + ); + self.handle_allowance(maker_swap_v2_contract, payment_amount, args.time_lock) + .await?; + self.sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(maker_swap_v2_contract), + data, + U256::from(self.gas_limit_v2.maker.erc20_payment), + ) + .compat() + .await + }, + EthCoinType::Nft { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( + "NFT protocol is not supported for ETH and ERC20 Swaps" + ))), + } + } + + pub(crate) async fn validate_maker_payment_v2_impl( + &self, + args: ValidateMakerPaymentArgs<'_, Self>, + ) -> ValidatePaymentResult<()> { + if let EthCoinType::Nft { .. } = self.coin_type { + return MmError::err(ValidatePaymentError::ProtocolNotSupported( + "NFT protocol is not supported for ETH and ERC20 Swaps".to_string(), + )); + } + let maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + ValidatePaymentError::InternalError("Expected swap_v2_contracts to be Some, but found None".to_string()) + })? + .maker_swap_v2_contract; + let taker_secret_hash = args.taker_secret_hash.try_into()?; + let maker_secret_hash = args.maker_secret_hash.try_into()?; + validate_amount(&args.amount).map_to_mm(ValidatePaymentError::InternalError)?; + let swap_id = self.etomic_swap_id_v2(args.time_lock, args.maker_secret_hash); + let maker_status = self + .payment_status_v2( + maker_swap_v2_contract, + Token::FixedBytes(swap_id.clone()), + &MAKER_SWAP_V2, + EthPaymentType::MakerPayments, + MAKER_PAYMENT_STATE_INDEX, + ) + .await?; + + let tx_from_rpc = self + .transaction(TransactionId::Hash(args.maker_payment_tx.tx_hash())) + .await?; + let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { + ValidatePaymentError::TxDoesNotExist(format!( + "Didn't find provided tx {:?} on ETH node", + args.maker_payment_tx.tx_hash() + )) + })?; + let maker_address = public_to_address(args.maker_pub); + validate_from_to_and_status( + tx_from_rpc, + maker_address, + maker_swap_v2_contract, + maker_status, + MakerPaymentStateV2::PaymentSent as u8, + )?; + + let validation_args = { + let amount = wei_from_big_decimal(&args.amount, self.decimals)?; + MakerValidationArgs { + swap_id, + amount, + taker: self.my_addr().await, + taker_secret_hash, + maker_secret_hash, + payment_time_lock: args.time_lock, + } + }; + match self.coin_type { + EthCoinType::Eth => { + let function = MAKER_SWAP_V2.function(ETH_MAKER_PAYMENT)?; + let decoded = decode_contract_call(function, &tx_from_rpc.input.0)?; + validate_eth_maker_payment_data(&decoded, &validation_args, function, tx_from_rpc.value)?; + }, + EthCoinType::Erc20 { token_addr, .. } => { + let function = MAKER_SWAP_V2.function(ERC20_MAKER_PAYMENT)?; + let decoded = decode_contract_call(function, &tx_from_rpc.input.0)?; + validate_erc20_maker_payment_data(&decoded, &validation_args, function, token_addr)?; + }, + EthCoinType::Nft { .. } => { + return MmError::err(ValidatePaymentError::ProtocolNotSupported( + "NFT protocol is not supported for ETH and ERC20 Swaps".to_string(), + )); + }, + } + Ok(()) + } + + pub(crate) async fn refund_maker_payment_v2_timelock_impl( + &self, + args: RefundMakerPaymentTimelockArgs<'_>, + ) -> Result { + let token_address = self + .get_token_address() + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + let maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))? + .maker_swap_v2_contract; + let gas_limit = self + .gas_limit_v2 + .gas_limit( + &self.coin_type, + EthPaymentType::MakerPayments, + PaymentMethod::RefundTimelock, + ) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + + let (maker_secret_hash, taker_secret_hash) = match args.tx_type_with_secret_hash { + SwapTxTypeWithSecretHash::MakerPaymentV2 { + maker_secret_hash, + taker_secret_hash, + } => (maker_secret_hash, taker_secret_hash), + _ => { + return Err(TransactionErr::Plain(ERRL!( + "Unsupported swap tx type for timelock refund" + ))) + }, + }; + let payment_amount = try_tx_s!(wei_from_big_decimal(&args.amount, self.decimals)); + + let args = { + let taker_address = public_to_address(&Public::from_slice(args.taker_pub)); + MakerRefundTimelockArgs { + payment_amount, + taker_address, + taker_secret_hash: try_tx_s!(taker_secret_hash.try_into()), + maker_secret_hash: try_tx_s!(maker_secret_hash.try_into()), + payment_time_lock: args.time_lock, + token_address, + } + }; + let data = try_tx_s!(self.prepare_refund_maker_payment_timelock_data(args).await); + + self.sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(maker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await + } + + pub(crate) async fn refund_maker_payment_v2_secret_impl( + &self, + args: RefundMakerPaymentSecretArgs<'_, Self>, + ) -> Result { + let token_address = self + .get_token_address() + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + let maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))? + .maker_swap_v2_contract; + let gas_limit = self + .gas_limit_v2 + .gas_limit( + &self.coin_type, + EthPaymentType::MakerPayments, + PaymentMethod::RefundSecret, + ) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + + let taker_secret = try_tx_s!(args.taker_secret.try_into()); + let maker_secret_hash = try_tx_s!(args.maker_secret_hash.try_into()); + let payment_amount = try_tx_s!(wei_from_big_decimal(&args.amount, self.decimals)); + let args = { + let taker_address = public_to_address(args.taker_pub); + MakerRefundSecretArgs { + payment_amount, + taker_address, + taker_secret, + maker_secret_hash, + payment_time_lock: args.time_lock, + token_address, + } + }; + let data = try_tx_s!(self.prepare_refund_maker_payment_secret_data(args).await); + + self.sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(maker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await + } + + pub(crate) async fn spend_maker_payment_v2_impl( + &self, + args: SpendMakerPaymentArgs<'_, Self>, + ) -> Result { + let token_address = self + .get_token_address() + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + let maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))? + .maker_swap_v2_contract; + let gas_limit = self + .gas_limit_v2 + .gas_limit(&self.coin_type, EthPaymentType::MakerPayments, PaymentMethod::Spend) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + + let data = try_tx_s!(self.prepare_spend_maker_payment_data(args, token_address).await); + + self.sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(maker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await + } + + /// Prepares data for EtomicSwapMakerV2 contract [ethMakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerV2.sol#L30) method + async fn prepare_maker_eth_payment_data(&self, args: &MakerPaymentArgs) -> Result, PrepareTxDataError> { + let function = MAKER_SWAP_V2.function(ETH_MAKER_PAYMENT)?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Address(args.taker_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Uint(args.payment_time_lock.into()), + ])?; + Ok(data) + } + + /// Prepares data for EtomicSwapMakerV2 contract [erc20MakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerV2.sol#L64) method + async fn prepare_maker_erc20_payment_data( + &self, + args: &MakerPaymentArgs, + payment_amount: U256, + token_address: Address, + ) -> Result, PrepareTxDataError> { + let function = MAKER_SWAP_V2.function(ERC20_MAKER_PAYMENT)?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(payment_amount), + Token::Address(token_address), + Token::Address(args.taker_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Uint(args.payment_time_lock.into()), + ])?; + Ok(data) + } + + /// Prepares data for EtomicSwapMakerV2 contract [refundMakerPaymentTimelock](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerV2.sol#L144) method + async fn prepare_refund_maker_payment_timelock_data( + &self, + args: MakerRefundTimelockArgs, + ) -> Result, PrepareTxDataError> { + let function = MAKER_SWAP_V2.function("refundMakerPaymentTimelock")?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(args.payment_amount), + Token::Address(args.taker_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Address(args.token_address), + ])?; + Ok(data) + } + + /// Prepares data for EtomicSwapMakerV2 contract [refundMakerPaymentSecret](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerV2.sol#L190) method + async fn prepare_refund_maker_payment_secret_data( + &self, + args: MakerRefundSecretArgs, + ) -> Result, PrepareTxDataError> { + let function = MAKER_SWAP_V2.function("refundMakerPaymentSecret")?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(args.payment_amount), + Token::Address(args.taker_address), + Token::FixedBytes(args.taker_secret.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Address(args.token_address), + ])?; + Ok(data) + } + + /// Prepares data for EtomicSwapMakerV2 contract [spendMakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerV2.sol#L104) method + async fn prepare_spend_maker_payment_data( + &self, + args: SpendMakerPaymentArgs<'_, Self>, + token_address: Address, + ) -> Result, PrepareTxDataError> { + let function = MAKER_SWAP_V2.function("spendMakerPayment")?; + let id = self.etomic_swap_id_v2(args.time_lock, args.maker_secret_hash); + let maker_address = public_to_address(args.maker_pub); + let payment_amount = wei_from_big_decimal(&args.amount, self.decimals) + .map_err(|e| PrepareTxDataError::Internal(e.to_string()))?; + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(payment_amount), + Token::Address(maker_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret.to_vec()), + Token::Address(token_address), + ])?; + Ok(data) + } +} + +/// Validation function for ETH maker payment data +fn validate_eth_maker_payment_data( + decoded: &[Token], + args: &MakerValidationArgs, + func: &Function, + tx_value: U256, +) -> Result<(), MmError> { + let checks = vec![ + (0, Token::FixedBytes(args.swap_id.clone()), "id"), + (1, Token::Address(args.taker), "taker"), + (2, Token::FixedBytes(args.taker_secret_hash.to_vec()), "takerSecretHash"), + (3, Token::FixedBytes(args.maker_secret_hash.to_vec()), "makerSecretHash"), + (4, Token::Uint(U256::from(args.payment_time_lock)), "paymentLockTime"), + ]; + + for (index, expected_token, field_name) in checks { + let token = get_function_input_data(decoded, func, index).map_to_mm(ValidatePaymentError::InternalError)?; + if token != expected_token { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "ETH Maker Payment `{}` {:?} is invalid, expected {:?}", + field_name, + decoded.get(index), + expected_token + ))); + } + } + if args.amount != tx_value { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "ETH Maker Payment amount, is invalid, expected {:?}, got {:?}", + args.amount, tx_value + ))); + } + Ok(()) +} + +/// Validation function for ERC20 maker payment data +fn validate_erc20_maker_payment_data( + decoded: &[Token], + args: &MakerValidationArgs, + func: &Function, + token_addr: Address, +) -> Result<(), MmError> { + let checks = vec![ + (0, Token::FixedBytes(args.swap_id.clone()), "id"), + (1, Token::Uint(args.amount), "amount"), + (2, Token::Address(token_addr), "tokenAddress"), + (3, Token::Address(args.taker), "taker"), + (4, Token::FixedBytes(args.taker_secret_hash.to_vec()), "takerSecretHash"), + (5, Token::FixedBytes(args.maker_secret_hash.to_vec()), "makerSecretHash"), + (6, Token::Uint(U256::from(args.payment_time_lock)), "paymentLockTime"), + ]; + + for (index, expected_token, field_name) in checks { + let token = get_function_input_data(decoded, func, index).map_to_mm(ValidatePaymentError::InternalError)?; + if token != expected_token { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "ERC20 Maker Payment `{}` {:?} is invalid, expected {:?}", + field_name, + decoded.get(index), + expected_token + ))); + } + } + Ok(()) +} diff --git a/mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs b/mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs new file mode 100644 index 0000000000..fea91b0408 --- /dev/null +++ b/mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs @@ -0,0 +1,768 @@ +use super::{check_decoded_length, validate_amount, validate_from_to_and_status, validate_payment_state, + EthPaymentType, PaymentMethod, PrepareTxDataError, ZERO_VALUE}; +use crate::eth::{decode_contract_call, get_function_input_data, wei_from_big_decimal, EthCoin, EthCoinType, + ParseCoinAssocTypes, RefundFundingSecretArgs, RefundTakerPaymentArgs, SendTakerFundingArgs, + SignedEthTx, SwapTxTypeWithSecretHash, TakerPaymentStateV2, TransactionErr, ValidateSwapV2TxError, + ValidateSwapV2TxResult, ValidateTakerFundingArgs, TAKER_SWAP_V2}; +use crate::{FundingTxSpend, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, SearchForFundingSpendErr, + WaitForPaymentSpendError}; +use common::executor::Timer; +use common::now_sec; +use ethabi::{Function, Token}; +use ethcore_transaction::Action; +use ethereum_types::{Address, Public, U256}; +use ethkey::public_to_address; +use futures::compat::Future01CompatExt; +use mm2_err_handle::prelude::{MapToMmResult, MmError, MmResult}; +use std::convert::TryInto; +use web3::types::TransactionId; + +const ETH_TAKER_PAYMENT: &str = "ethTakerPayment"; +const ERC20_TAKER_PAYMENT: &str = "erc20TakerPayment"; +const TAKER_PAYMENT_APPROVE: &str = "takerPaymentApprove"; + +/// state index for `TakerPayment` structure from `EtomicSwapTakerV2.sol` +/// +/// struct TakerPayment { +/// bytes20 paymentHash; +/// uint32 preApproveLockTime; +/// uint32 paymentLockTime; +/// TakerPaymentState state; +/// } +const TAKER_PAYMENT_STATE_INDEX: usize = 3; + +struct TakerFundingArgs { + dex_fee: U256, + payment_amount: U256, + maker_address: Address, + taker_secret_hash: [u8; 32], + maker_secret_hash: [u8; 32], + funding_time_lock: u64, + payment_time_lock: u64, +} + +struct TakerRefundTimelockArgs { + dex_fee: U256, + payment_amount: U256, + maker_address: Address, + taker_secret_hash: [u8; 32], + maker_secret_hash: [u8; 32], + payment_time_lock: u64, + token_address: Address, +} + +struct TakerRefundSecretArgs { + dex_fee: U256, + payment_amount: U256, + maker_address: Address, + taker_secret: [u8; 32], + maker_secret_hash: [u8; 32], + payment_time_lock: u64, + token_address: Address, +} + +struct TakerValidationArgs<'a> { + swap_id: Vec, + amount: U256, + dex_fee: U256, + receiver: Address, + taker_secret_hash: &'a [u8; 32], + maker_secret_hash: &'a [u8; 32], + funding_time_lock: u64, + payment_time_lock: u64, +} + +impl EthCoin { + /// Calls `"ethTakerPayment"` or `"erc20TakerPayment"` swap contract methods. + /// Returns taker sent payment transaction. + pub(crate) async fn send_taker_funding_impl( + &self, + args: SendTakerFundingArgs<'_>, + ) -> Result { + let taker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))? + .taker_swap_v2_contract; + // TODO add burnFee support + let dex_fee = try_tx_s!(wei_from_big_decimal(&args.dex_fee.fee_amount().into(), self.decimals)); + + let payment_amount = try_tx_s!(wei_from_big_decimal( + &(args.trading_amount.clone() + args.premium_amount.clone()), + self.decimals + )); + let funding_args = { + let maker_address = public_to_address(&Public::from_slice(args.maker_pub)); + TakerFundingArgs { + dex_fee, + payment_amount, + maker_address, + taker_secret_hash: try_tx_s!(args.taker_secret_hash.try_into()), + maker_secret_hash: try_tx_s!(args.maker_secret_hash.try_into()), + funding_time_lock: args.funding_time_lock, + payment_time_lock: args.payment_time_lock, + } + }; + match &self.coin_type { + EthCoinType::Eth => { + let data = try_tx_s!(self.prepare_taker_eth_funding_data(&funding_args).await); + let eth_total_payment = payment_amount.checked_add(dex_fee).ok_or_else(|| { + TransactionErr::Plain(ERRL!("Overflow occurred while calculating eth_total_payment")) + })?; + self.sign_and_send_transaction( + eth_total_payment, + Action::Call(taker_swap_v2_contract), + data, + U256::from(self.gas_limit_v2.taker.eth_payment), + ) + .compat() + .await + }, + EthCoinType::Erc20 { + platform: _, + token_addr, + } => { + let data = try_tx_s!(self.prepare_taker_erc20_funding_data(&funding_args, *token_addr).await); + self.handle_allowance(taker_swap_v2_contract, payment_amount, args.funding_time_lock) + .await?; + self.sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(taker_swap_v2_contract), + data, + U256::from(self.gas_limit_v2.taker.erc20_payment), + ) + .compat() + .await + }, + EthCoinType::Nft { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( + "NFT protocol is not supported for ETH and ERC20 Swaps" + ))), + } + } + + pub(crate) async fn validate_taker_funding_impl( + &self, + args: ValidateTakerFundingArgs<'_, Self>, + ) -> ValidateSwapV2TxResult { + if let EthCoinType::Nft { .. } = self.coin_type { + return MmError::err(ValidateSwapV2TxError::ProtocolNotSupported( + "NFT protocol is not supported for ETH and ERC20 Swaps".to_string(), + )); + } + let taker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + ValidateSwapV2TxError::Internal("Expected swap_v2_contracts to be Some, but found None".to_string()) + })? + .taker_swap_v2_contract; + let taker_secret_hash = args.taker_secret_hash.try_into()?; + let maker_secret_hash = args.maker_secret_hash.try_into()?; + validate_amount(&args.trading_amount).map_err(ValidateSwapV2TxError::Internal)?; + let swap_id = self.etomic_swap_id_v2(args.payment_time_lock, args.maker_secret_hash); + let taker_status = self + .payment_status_v2( + taker_swap_v2_contract, + Token::FixedBytes(swap_id.clone()), + &TAKER_SWAP_V2, + EthPaymentType::TakerPayments, + TAKER_PAYMENT_STATE_INDEX, + ) + .await?; + + let tx_from_rpc = self.transaction(TransactionId::Hash(args.funding_tx.tx_hash())).await?; + let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { + ValidateSwapV2TxError::TxDoesNotExist(format!( + "Didn't find provided tx {:?} on ETH node", + args.funding_tx.tx_hash() + )) + })?; + let taker_address = public_to_address(args.taker_pub); + validate_from_to_and_status( + tx_from_rpc, + taker_address, + taker_swap_v2_contract, + taker_status, + TakerPaymentStateV2::PaymentSent as u8, + )?; + + let validation_args = { + let dex_fee = wei_from_big_decimal(&args.dex_fee.fee_amount().into(), self.decimals)?; + let payment_amount = wei_from_big_decimal(&(args.trading_amount + args.premium_amount), self.decimals)?; + TakerValidationArgs { + swap_id, + amount: payment_amount, + dex_fee, + receiver: self.my_addr().await, + taker_secret_hash, + maker_secret_hash, + funding_time_lock: args.funding_time_lock, + payment_time_lock: args.payment_time_lock, + } + }; + match self.coin_type { + EthCoinType::Eth => { + let function = TAKER_SWAP_V2.function(ETH_TAKER_PAYMENT)?; + let decoded = decode_contract_call(function, &tx_from_rpc.input.0)?; + validate_eth_taker_payment_data(&decoded, &validation_args, function, tx_from_rpc.value)?; + }, + EthCoinType::Erc20 { token_addr, .. } => { + let function = TAKER_SWAP_V2.function(ERC20_TAKER_PAYMENT)?; + let decoded = decode_contract_call(function, &tx_from_rpc.input.0)?; + validate_erc20_taker_payment_data(&decoded, &validation_args, function, token_addr)?; + }, + EthCoinType::Nft { .. } => { + return MmError::err(ValidateSwapV2TxError::ProtocolNotSupported( + "NFT protocol is not supported for ETH and ERC20 Swaps".to_string(), + )); + }, + } + Ok(()) + } + + /// Taker approves payment calling `takerPaymentApprove` for EVM based chains. + /// Function accepts taker payment transaction, returns taker approve payment transaction. + pub(crate) async fn taker_payment_approve( + &self, + args: &GenTakerFundingSpendArgs<'_, Self>, + ) -> Result { + let gas_limit = match self.coin_type { + EthCoinType::Eth | EthCoinType::Erc20 { .. } => U256::from(self.gas_limit_v2.taker.approve_payment), + EthCoinType::Nft { .. } => { + return Err(TransactionErr::ProtocolNotSupported(ERRL!( + "NFT protocol is not supported for ETH and ERC20 Swaps" + ))) + }, + }; + let (taker_swap_v2_contract, send_func, token_address) = self + .taker_swap_v2_details(ETH_TAKER_PAYMENT, ERC20_TAKER_PAYMENT) + .await?; + let decoded = try_tx_s!(decode_contract_call(send_func, args.funding_tx.unsigned().data())); + let taker_status = try_tx_s!( + self.payment_status_v2( + taker_swap_v2_contract, + decoded[0].clone(), + &TAKER_SWAP_V2, + EthPaymentType::TakerPayments, + TAKER_PAYMENT_STATE_INDEX, + ) + .await + ); + validate_payment_state(args.funding_tx, taker_status, TakerPaymentStateV2::PaymentSent as u8) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + let data = try_tx_s!( + self.prepare_taker_payment_approve_data(args, decoded, token_address) + .await + ); + let approve_tx = self + .sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(taker_swap_v2_contract), + data, + gas_limit, + ) + .compat() + .await?; + Ok(approve_tx) + } + + pub(crate) async fn refund_taker_payment_with_timelock_impl( + &self, + args: RefundTakerPaymentArgs<'_>, + ) -> Result { + let token_address = self + .get_token_address() + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + let taker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))? + .taker_swap_v2_contract; + let gas_limit = self + .gas_limit_v2 + .gas_limit( + &self.coin_type, + EthPaymentType::TakerPayments, + PaymentMethod::RefundTimelock, + ) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + + let (maker_secret_hash, taker_secret_hash) = match args.tx_type_with_secret_hash { + SwapTxTypeWithSecretHash::TakerPaymentV2 { + maker_secret_hash, + taker_secret_hash, + } => (maker_secret_hash, taker_secret_hash), + _ => { + return Err(TransactionErr::Plain(ERRL!( + "Unsupported swap tx type for timelock refund" + ))) + }, + }; + let dex_fee = try_tx_s!(wei_from_big_decimal( + &args.dex_fee.fee_amount().to_decimal(), + self.decimals + )); + let payment_amount = try_tx_s!(wei_from_big_decimal( + &(args.trading_amount + args.premium_amount), + self.decimals + )); + + let args = { + let maker_address = public_to_address(&Public::from_slice(args.maker_pub)); + TakerRefundTimelockArgs { + dex_fee, + payment_amount, + maker_address, + taker_secret_hash: try_tx_s!(taker_secret_hash.try_into()), + maker_secret_hash: try_tx_s!(maker_secret_hash.try_into()), + payment_time_lock: args.time_lock, + token_address, + } + }; + let data = try_tx_s!(self.prepare_taker_refund_payment_timelock_data(args).await); + + self.sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(taker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await + } + + pub(crate) async fn refund_taker_funding_secret_impl( + &self, + args: RefundFundingSecretArgs<'_, Self>, + ) -> Result { + let token_address = self + .get_token_address() + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + let taker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))? + .taker_swap_v2_contract; + let gas_limit = self + .gas_limit_v2 + .gas_limit( + &self.coin_type, + EthPaymentType::TakerPayments, + PaymentMethod::RefundSecret, + ) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + + let taker_secret = try_tx_s!(args.taker_secret.try_into()); + let maker_secret_hash = try_tx_s!(args.maker_secret_hash.try_into()); + let dex_fee = try_tx_s!(wei_from_big_decimal( + &args.dex_fee.fee_amount().to_decimal(), + self.decimals + )); + let payment_amount = try_tx_s!(wei_from_big_decimal( + &(args.trading_amount + args.premium_amount), + self.decimals + )); + + let refund_args = { + let maker_address = public_to_address(args.maker_pubkey); + TakerRefundSecretArgs { + dex_fee, + payment_amount, + maker_address, + taker_secret, + maker_secret_hash, + payment_time_lock: args.payment_time_lock, + token_address, + } + }; + let data = try_tx_s!(self.prepare_taker_refund_payment_secret_data(&refund_args).await); + + self.sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(taker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await + } + + /// Checks that taker payment state is `TakerApproved`. + /// Accepts a taker-approved payment transaction and returns it if the state is correct. + pub(crate) async fn search_for_taker_funding_spend_impl( + &self, + tx: &SignedEthTx, + ) -> Result>, SearchForFundingSpendErr> { + let (decoded, taker_swap_v2_contract) = self + .get_decoded_and_swap_contract(tx, TAKER_PAYMENT_APPROVE) + .await + .map_err(|e| SearchForFundingSpendErr::Internal(ERRL!("{}", e)))?; + let taker_status = self + .payment_status_v2( + taker_swap_v2_contract, + decoded[0].clone(), // id from takerPaymentApprove + &TAKER_SWAP_V2, + EthPaymentType::TakerPayments, + TAKER_PAYMENT_STATE_INDEX, + ) + .await + .map_err(|e| SearchForFundingSpendErr::Internal(ERRL!("{}", e)))?; + if taker_status == U256::from(TakerPaymentStateV2::TakerApproved as u8) { + return Ok(Some(FundingTxSpend::TransferredToTakerPayment(tx.clone()))); + } + Ok(None) + } + + /// Taker swap contract `spendTakerPayment` method is called for EVM based chains. + /// Returns maker spent payment transaction. + pub(crate) async fn sign_and_broadcast_taker_payment_spend_impl( + &self, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, + secret: &[u8], + ) -> Result { + let gas_limit = self + .gas_limit_v2 + .gas_limit(&self.coin_type, EthPaymentType::TakerPayments, PaymentMethod::Spend) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + + let (taker_swap_v2_contract, approve_func, token_address) = self + .taker_swap_v2_details(TAKER_PAYMENT_APPROVE, TAKER_PAYMENT_APPROVE) + .await?; + let decoded = try_tx_s!(decode_contract_call(approve_func, gen_args.taker_tx.unsigned().data())); + let taker_status = try_tx_s!( + self.payment_status_v2( + taker_swap_v2_contract, + decoded[0].clone(), + &TAKER_SWAP_V2, + EthPaymentType::TakerPayments, + TAKER_PAYMENT_STATE_INDEX, + ) + .await + ); + validate_payment_state( + gen_args.taker_tx, + taker_status, + TakerPaymentStateV2::TakerApproved as u8, + ) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + let data = try_tx_s!( + self.prepare_spend_taker_payment_data(gen_args, secret, decoded, token_address) + .await + ); + let spend_payment_tx = self + .sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(taker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await?; + Ok(spend_payment_tx) + } + + /// Checks that taker payment state is `MakerSpent`. + /// Accepts maker spent payment transaction and returns it if payment status is correct. + pub(crate) async fn wait_for_taker_payment_spend_impl( + &self, + taker_payment: &SignedEthTx, + wait_until: u64, + ) -> MmResult { + let (decoded, taker_swap_v2_contract) = self + .get_decoded_and_swap_contract(taker_payment, "spendTakerPayment") + .await?; + loop { + let taker_status = self + .payment_status_v2( + taker_swap_v2_contract, + decoded[0].clone(), // id from spendTakerPayment + &TAKER_SWAP_V2, + EthPaymentType::TakerPayments, + TAKER_PAYMENT_STATE_INDEX, + ) + .await?; + if taker_status == U256::from(TakerPaymentStateV2::MakerSpent as u8) { + return Ok(taker_payment.clone()); + } + let now = now_sec(); + if now > wait_until { + return MmError::err(WaitForPaymentSpendError::Timeout { wait_until, now }); + } + Timer::sleep(10.).await; + } + } + + /// Prepares data for EtomicSwapTakerV2 contract [ethTakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L44) method + async fn prepare_taker_eth_funding_data(&self, args: &TakerFundingArgs) -> Result, PrepareTxDataError> { + let function = TAKER_SWAP_V2.function(ETH_TAKER_PAYMENT)?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(args.dex_fee), + Token::Address(args.maker_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Uint(args.funding_time_lock.into()), + Token::Uint(args.payment_time_lock.into()), + ])?; + Ok(data) + } + + /// Prepares data for EtomicSwapTakerV2 contract [erc20TakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L83) method + async fn prepare_taker_erc20_funding_data( + &self, + args: &TakerFundingArgs, + token_address: Address, + ) -> Result, PrepareTxDataError> { + let function = TAKER_SWAP_V2.function(ERC20_TAKER_PAYMENT)?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(args.payment_amount), + Token::Uint(args.dex_fee), + Token::Address(token_address), + Token::Address(args.maker_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Uint(args.funding_time_lock.into()), + Token::Uint(args.payment_time_lock.into()), + ])?; + Ok(data) + } + + /// Prepares data for EtomicSwapTakerV2 contract [refundTakerPaymentTimelock](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L208) method + async fn prepare_taker_refund_payment_timelock_data( + &self, + args: TakerRefundTimelockArgs, + ) -> Result, PrepareTxDataError> { + let function = TAKER_SWAP_V2.function("refundTakerPaymentTimelock")?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(args.payment_amount), + Token::Uint(args.dex_fee), + Token::Address(args.maker_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Address(args.token_address), + ])?; + Ok(data) + } + + /// Prepares data for EtomicSwapTakerV2 contract [refundTakerPaymentSecret](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L267) method + async fn prepare_taker_refund_payment_secret_data( + &self, + args: &TakerRefundSecretArgs, + ) -> Result, PrepareTxDataError> { + let function = TAKER_SWAP_V2.function("refundTakerPaymentSecret")?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(args.payment_amount), + Token::Uint(args.dex_fee), + Token::Address(args.maker_address), + Token::FixedBytes(args.taker_secret.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Address(args.token_address), + ])?; + Ok(data) + } + + /// This function constructs the encoded transaction input data required to approve the taker payment ([takerPaymentApprove](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L128)). + /// The `decoded` parameter should contain the transaction input data from the `ethTakerPayment` or `erc20TakerPayment` function of the EtomicSwapTakerV2 contract. + async fn prepare_taker_payment_approve_data( + &self, + args: &GenTakerFundingSpendArgs<'_, Self>, + decoded: Vec, + token_address: Address, + ) -> Result, PrepareTxDataError> { + let function = TAKER_SWAP_V2.function(TAKER_PAYMENT_APPROVE)?; + let data = match self.coin_type { + EthCoinType::Eth => { + check_decoded_length(&decoded, 7)?; + let dex_fee = match &decoded[1] { + Token::Uint(value) => value, + _ => return Err(PrepareTxDataError::Internal("Invalid token type for dex fee".into())), + }; + let amount = args + .funding_tx + .unsigned() + .value() + .checked_sub(*dex_fee) + .ok_or_else(|| { + PrepareTxDataError::Internal("Underflow occurred while calculating amount".into()) + })?; + function.encode_input(&[ + decoded[0].clone(), // id from ethTakerPayment + Token::Uint(amount), // calculated payment amount (tx value - dexFee) + decoded[1].clone(), // dexFee from ethTakerPayment + decoded[2].clone(), // receiver from ethTakerPayment + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Address(token_address), // should be zero address Address::default() + ])? + }, + EthCoinType::Erc20 { .. } => { + check_decoded_length(&decoded, 9)?; + function.encode_input(&[ + decoded[0].clone(), // id from erc20TakerPayment + decoded[1].clone(), // amount from erc20TakerPayment + decoded[2].clone(), // dexFee from erc20TakerPayment + decoded[4].clone(), // receiver from erc20TakerPayment + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Address(token_address), // erc20 token address from EthCoinType::Erc20 + ])? + }, + EthCoinType::Nft { .. } => { + return Err(PrepareTxDataError::Internal("EthCoinType must be ETH or ERC20".into())) + }, + }; + Ok(data) + } + + /// Prepares data for EtomicSwapTakerV2 contract [spendTakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L164) method + async fn prepare_spend_taker_payment_data( + &self, + args: &GenTakerPaymentSpendArgs<'_, Self>, + secret: &[u8], + decoded: Vec, + token_address: Address, + ) -> Result, PrepareTxDataError> { + check_decoded_length(&decoded, 7)?; + let function = TAKER_SWAP_V2.function("spendTakerPayment")?; + let taker_address = public_to_address(args.taker_pub); + let data = function.encode_input(&[ + decoded[0].clone(), // id from takerPaymentApprove + decoded[1].clone(), // amount from takerPaymentApprove + decoded[2].clone(), // dexFee from takerPaymentApprove + Token::Address(taker_address), // taker address + decoded[4].clone(), // takerSecretHash from ethTakerPayment + Token::FixedBytes(secret.to_vec()), // makerSecret + Token::Address(token_address), // tokenAddress + ])?; + Ok(data) + } + + /// Retrieves the taker smart contract address, the corresponding function, and the token address. + /// + /// Depending on the coin type (ETH or ERC20), it fetches the appropriate function name and token address. + /// Returns an error if the coin type is NFT or if the `swap_v2_contracts` is None. + async fn taker_swap_v2_details( + &self, + eth_func_name: &str, + erc20_func_name: &str, + ) -> Result<(Address, &Function, Address), TransactionErr> { + let (func, token_address) = match self.coin_type { + EthCoinType::Eth => (try_tx_s!(TAKER_SWAP_V2.function(eth_func_name)), Address::default()), + EthCoinType::Erc20 { token_addr, .. } => (try_tx_s!(TAKER_SWAP_V2.function(erc20_func_name)), token_addr), + EthCoinType::Nft { .. } => { + return Err(TransactionErr::ProtocolNotSupported(ERRL!( + "NFT protocol is not supported for ETH and ERC20 Swaps" + ))) + }, + }; + let taker_swap_v2_contract = self + .swap_v2_contracts + .as_ref() + .map(|contracts| contracts.taker_swap_v2_contract) + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))?; + Ok((taker_swap_v2_contract, func, token_address)) + } + + async fn get_decoded_and_swap_contract( + &self, + tx: &SignedEthTx, + function_name: &str, + ) -> Result<(Vec, Address), PrepareTxDataError> { + let decoded = { + let func = match self.coin_type { + EthCoinType::Eth | EthCoinType::Erc20 { .. } => TAKER_SWAP_V2.function(function_name)?, + EthCoinType::Nft { .. } => { + return Err(PrepareTxDataError::Internal( + "NFT protocol is not supported for ETH and ERC20 Swaps".to_string(), + )); + }, + }; + decode_contract_call(func, tx.unsigned().data())? + }; + let taker_swap_v2_contract = self + .swap_v2_contracts + .as_ref() + .map(|contracts| contracts.taker_swap_v2_contract) + .ok_or_else(|| { + PrepareTxDataError::Internal("Expected swap_v2_contracts to be Some, but found None".to_string()) + })?; + + Ok((decoded, taker_swap_v2_contract)) + } +} + +/// Validation function for ETH taker payment data +fn validate_eth_taker_payment_data( + decoded: &[Token], + args: &TakerValidationArgs, + func: &Function, + tx_value: U256, +) -> Result<(), MmError> { + let checks = vec![ + (0, Token::FixedBytes(args.swap_id.clone()), "id"), + (1, Token::Uint(args.dex_fee), "dexFee"), + (2, Token::Address(args.receiver), "receiver"), + (3, Token::FixedBytes(args.taker_secret_hash.to_vec()), "takerSecretHash"), + (4, Token::FixedBytes(args.maker_secret_hash.to_vec()), "makerSecretHash"), + (5, Token::Uint(U256::from(args.funding_time_lock)), "preApproveLockTime"), + (6, Token::Uint(U256::from(args.payment_time_lock)), "paymentLockTime"), + ]; + + for (index, expected_token, field_name) in checks { + let token = get_function_input_data(decoded, func, index).map_to_mm(ValidateSwapV2TxError::Internal)?; + if token != expected_token { + return MmError::err(ValidateSwapV2TxError::WrongPaymentTx(format!( + "ETH Taker Payment `{}` {:?} is invalid, expected {:?}", + field_name, + decoded.get(index), + expected_token + ))); + } + } + let total = args.amount.checked_add(args.dex_fee).ok_or_else(|| { + ValidateSwapV2TxError::Overflow("Overflow occurred while calculating total payment".to_string()) + })?; + if total != tx_value { + return MmError::err(ValidateSwapV2TxError::WrongPaymentTx(format!( + "ETH Taker Payment amount, is invalid, expected {:?}, got {:?}", + total, tx_value + ))); + } + Ok(()) +} + +/// Validation function for ERC20 taker payment data +fn validate_erc20_taker_payment_data( + decoded: &[Token], + args: &TakerValidationArgs, + func: &Function, + token_addr: Address, +) -> Result<(), MmError> { + let checks = vec![ + (0, Token::FixedBytes(args.swap_id.clone()), "id"), + (1, Token::Uint(args.amount), "amount"), + (2, Token::Uint(args.dex_fee), "dexFee"), + (3, Token::Address(token_addr), "tokenAddress"), + (4, Token::Address(args.receiver), "receiver"), + (5, Token::FixedBytes(args.taker_secret_hash.to_vec()), "takerSecretHash"), + (6, Token::FixedBytes(args.maker_secret_hash.to_vec()), "makerSecretHash"), + (7, Token::Uint(U256::from(args.funding_time_lock)), "preApproveLockTime"), + (8, Token::Uint(U256::from(args.payment_time_lock)), "paymentLockTime"), + ]; + + for (index, expected_token, field_name) in checks { + let token = get_function_input_data(decoded, func, index).map_to_mm(ValidateSwapV2TxError::Internal)?; + if token != expected_token { + return MmError::err(ValidateSwapV2TxError::WrongPaymentTx(format!( + "ERC20 Taker Payment `{}` {:?} is invalid, expected {:?}", + field_name, + decoded.get(index), + expected_token + ))); + } + } + Ok(()) +} diff --git a/mm2src/coins/eth/eth_swap_v2/mod.rs b/mm2src/coins/eth/eth_swap_v2/mod.rs new file mode 100644 index 0000000000..798a232d56 --- /dev/null +++ b/mm2src/coins/eth/eth_swap_v2/mod.rs @@ -0,0 +1,203 @@ +use crate::eth::{EthCoin, EthCoinType, ParseCoinAssocTypes, Transaction, TransactionErr}; +use enum_derives::EnumFromStringify; +use ethabi::{Contract, Token}; +use ethcore_transaction::SignedTransaction as SignedEthTx; +use ethereum_types::{Address, U256}; +use futures::compat::Future01CompatExt; +use mm2_err_handle::mm_error::MmError; +use mm2_number::BigDecimal; +use num_traits::Signed; +use web3::types::Transaction as Web3Tx; + +pub(crate) mod eth_maker_swap_v2; +pub(crate) mod eth_taker_swap_v2; + +/// ZERO_VALUE is used to represent a 0 amount in transactions where the value is encoded in the transaction input data. +/// This is typically used in function calls where the value is not directly transferred with the transaction, such as in +/// `spendTakerPayment` where the [amount](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L166) +/// is provided as part of the input data rather than as an Ether value +pub(crate) const ZERO_VALUE: u32 = 0; + +pub enum EthPaymentType { + MakerPayments, + TakerPayments, +} + +impl EthPaymentType { + pub(crate) fn as_str(&self) -> &'static str { + match self { + EthPaymentType::MakerPayments => "makerPayments", + EthPaymentType::TakerPayments => "takerPayments", + } + } +} + +pub enum PaymentMethod { + Send, + Spend, + RefundTimelock, + RefundSecret, +} + +#[derive(Debug, Display)] +pub(crate) enum ValidatePaymentV2Err { + UnexpectedPaymentState(String), + WrongPaymentTx(String), +} + +#[derive(Debug, Display, EnumFromStringify)] +pub(crate) enum PaymentStatusErr { + #[from_stringify("ethabi::Error")] + #[display(fmt = "ABI error: {}", _0)] + ABIError(String), + #[from_stringify("web3::Error")] + #[display(fmt = "Transport error: {}", _0)] + Transport(String), + #[display(fmt = "Internal error: {}", _0)] + Internal(String), + #[display(fmt = "Invalid data error: {}", _0)] + InvalidData(String), +} + +#[derive(Debug, Display, EnumFromStringify)] +pub(crate) enum PrepareTxDataError { + #[from_stringify("ethabi::Error")] + #[display(fmt = "ABI error: {}", _0)] + ABIError(String), + #[display(fmt = "Internal error: {}", _0)] + Internal(String), +} + +impl EthCoin { + /// Retrieves the payment status from a given smart contract address based on the swap ID and state type. + pub(crate) async fn payment_status_v2( + &self, + swap_address: Address, + swap_id: Token, + contract_abi: &Contract, + payment_type: EthPaymentType, + state_index: usize, + ) -> Result { + let function_name = payment_type.as_str(); + let function = contract_abi.function(function_name)?; + let data = function.encode_input(&[swap_id])?; + let bytes = self + .call_request(self.my_addr().await, swap_address, None, Some(data.into())) + .await?; + let decoded_tokens = function.decode_output(&bytes.0)?; + + let state = decoded_tokens.get(state_index).ok_or_else(|| { + PaymentStatusErr::Internal(format!( + "Payment status must contain 'state' as the {} token", + state_index + )) + })?; + match state { + Token::Uint(state) => Ok(*state), + _ => Err(PaymentStatusErr::InvalidData(format!( + "Payment status must be Uint, got {:?}", + state + ))), + } + } + + pub(super) fn get_token_address(&self) -> Result { + match &self.coin_type { + EthCoinType::Eth => Ok(Address::default()), + EthCoinType::Erc20 { token_addr, .. } => Ok(*token_addr), + EthCoinType::Nft { .. } => Err("NFT protocol is not supported for ETH and ERC20 Swaps".to_string()), + } + } +} + +pub(crate) fn validate_payment_state( + tx: &SignedEthTx, + state: U256, + expected_state: u8, +) -> Result<(), PrepareTxDataError> { + if state != U256::from(expected_state) { + return Err(PrepareTxDataError::Internal(format!( + "Payment {:?} state is not `{}`, got `{}`", + tx, expected_state, state + ))); + } + Ok(()) +} + +pub(crate) fn validate_from_to_and_status( + tx_from_rpc: &Web3Tx, + expected_from: Address, + expected_to: Address, + status: U256, + expected_status: u8, +) -> Result<(), MmError> { + if status != U256::from(expected_status) { + return MmError::err(ValidatePaymentV2Err::UnexpectedPaymentState(format!( + "Payment state is not `PaymentSent`, got {}", + status + ))); + } + if tx_from_rpc.from != Some(expected_from) { + return MmError::err(ValidatePaymentV2Err::WrongPaymentTx(format!( + "Payment tx {:?} was sent from wrong address, expected {:?}", + tx_from_rpc, expected_from + ))); + } + // (in NFT case) as NFT owner calls "safeTransferFrom" directly, then in Transaction 'to' field we expect token_address + if tx_from_rpc.to != Some(expected_to) { + return MmError::err(ValidatePaymentV2Err::WrongPaymentTx(format!( + "Payment tx {:?} was sent to wrong address, expected {:?}", + tx_from_rpc, expected_to, + ))); + } + Ok(()) +} + +// TODO validate premium when add its support in swap_v2 +fn validate_amount(trading_amount: &BigDecimal) -> Result<(), String> { + if !trading_amount.is_positive() { + return Err("trading_amount must be a positive value".to_string()); + } + Ok(()) +} + +fn check_decoded_length(decoded: &Vec, expected_len: usize) -> Result<(), PrepareTxDataError> { + if decoded.len() != expected_len { + return Err(PrepareTxDataError::Internal(format!( + "Invalid number of tokens in decoded. Expected {}, found {}", + expected_len, + decoded.len() + ))); + } + Ok(()) +} + +impl EthCoin { + async fn handle_allowance( + &self, + swap_contract: Address, + payment_amount: U256, + time_lock: u64, + ) -> Result<(), TransactionErr> { + let allowed = self + .allowance(swap_contract) + .compat() + .await + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + + if allowed < payment_amount { + let approved_tx = self.approve(swap_contract, U256::max_value()).compat().await?; + self.wait_for_required_allowance(swap_contract, payment_amount, time_lock) + .compat() + .await + .map_err(|e| { + TransactionErr::Plain(ERRL!( + "Allowed value was not updated in time after sending approve transaction {:02x}: {}", + approved_tx.tx_hash_as_bytes(), + e + )) + })?; + } + Ok(()) + } +} diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 4d8b97c493..e799ba43d3 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -1,27 +1,31 @@ use super::*; -use crate::eth::for_tests::{eth_coin_for_test, eth_coin_from_keypair}; -use crate::{DexFee, IguanaPrivKey}; -use common::{block_on, now_sec}; -#[cfg(not(target_arch = "wasm32"))] -use ethkey::{Generator, Random}; +use crate::IguanaPrivKey; +use common::block_on; use mm2_core::mm_ctx::MmCtxBuilder; -use mm2_test_helpers::for_tests::{ETH_MAINNET_CHAIN_ID, ETH_MAINNET_NODE, ETH_SEPOLIA_CHAIN_ID, ETH_SEPOLIA_NODES, + +cfg_native!( + use crate::eth::for_tests::{eth_coin_for_test, eth_coin_from_keypair}; + use crate::DexFee; + + use common::{now_sec, block_on_f01}; + use ethkey::{Generator, Random}; + use mm2_test_helpers::for_tests::{ETH_MAINNET_CHAIN_ID, ETH_MAINNET_NODE, ETH_SEPOLIA_CHAIN_ID, ETH_SEPOLIA_NODES, ETH_SEPOLIA_TOKEN_CONTRACT}; -use mocktopus::mocking::*; - -/// The gas price for the tests -const GAS_PRICE: u64 = 50_000_000_000; -// `GAS_PRICE` increased by 3% -const GAS_PRICE_APPROXIMATION_ON_START_SWAP: u64 = 51_500_000_000; -// `GAS_PRICE` increased by 5% -const GAS_PRICE_APPROXIMATION_ON_ORDER_ISSUE: u64 = 52_500_000_000; -// `GAS_PRICE` increased by 7% -const GAS_PRICE_APPROXIMATION_ON_TRADE_PREIMAGE: u64 = 53_500_000_000; + use mocktopus::mocking::*; + + /// The gas price for the tests + const GAS_PRICE: u64 = 50_000_000_000; + /// `GAS_PRICE` increased by 3% + const GAS_PRICE_APPROXIMATION_ON_START_SWAP: u64 = 51_500_000_000; + /// `GAS_PRICE` increased by 5% + const GAS_PRICE_APPROXIMATION_ON_ORDER_ISSUE: u64 = 52_500_000_000; + /// `GAS_PRICE` increased by 7% + const GAS_PRICE_APPROXIMATION_ON_TRADE_PREIMAGE: u64 = 53_500_000_000; +); + // old way to add some extra gas to the returned value from gas station (non-existent now), still used in tests const GAS_PRICE_PERCENT: u64 = 10; -const TAKER_PAYMENT_SPEND_SEARCH_INTERVAL: f64 = 1.; - fn check_sum(addr: &str, expected: &str) { let actual = checksum_address(addr); assert_eq!(expected, actual); @@ -154,8 +158,11 @@ fn test_wei_from_big_decimal() { assert_eq!(expected_wei, wei); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn test_wait_for_payment_spend_timeout() { + const TAKER_PAYMENT_SPEND_SEARCH_INTERVAL: f64 = 1.; + EthCoin::spend_events.mock_safe(|_, _, _, _| MockResult::Return(Box::new(futures01::future::ok(vec![])))); EthCoin::current_block.mock_safe(|_| MockResult::Return(Box::new(futures01::future::ok(900)))); @@ -184,18 +191,16 @@ fn test_wait_for_payment_spend_timeout() { 184, 42, 106, ]; - assert!(coin - .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &tx_bytes, - secret_hash: &[], - wait_until, - from_block, - swap_contract_address: &coin.swap_contract_address(), - check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: false - }) - .wait() - .is_err()); + assert!(block_on(coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &tx_bytes, + secret_hash: &[], + wait_until, + from_block, + swap_contract_address: &coin.swap_contract_address(), + check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, + watcher_reward: false + })) + .is_err()); } #[cfg(not(target_arch = "wasm32"))] @@ -222,7 +227,7 @@ fn test_withdraw_impl_manual_fee() { memo: None, ibc_source_channel: None, }; - coin.get_balance().wait().unwrap(); + block_on_f01(coin.get_balance()).unwrap(); let tx_details = block_on(withdraw_impl(coin, withdraw_req)).unwrap(); let expected = Some( @@ -271,7 +276,7 @@ fn test_withdraw_impl_fee_details() { memo: None, ibc_source_channel: None, }; - coin.get_balance().wait().unwrap(); + block_on_f01(coin.get_balance()).unwrap(); let tx_details = block_on(withdraw_impl(coin, withdraw_req)).unwrap(); let expected = Some( @@ -306,6 +311,7 @@ fn test_add_ten_pct_one_gwei() { assert_eq!(expected, actual); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn get_sender_trade_preimage() { /// Trade fee for the ETH coin is `2 * 150_000 * gas_price` always. @@ -361,6 +367,7 @@ fn get_sender_trade_preimage() { assert_eq!(actual, expected); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn get_erc20_sender_trade_preimage() { const APPROVE_GAS_LIMIT: u64 = 60_000; @@ -463,6 +470,7 @@ fn get_erc20_sender_trade_preimage() { ); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn get_receiver_trade_preimage() { EthCoin::get_gas_price.mock_safe(|_| MockResult::Return(Box::pin(futures::future::ok(GAS_PRICE.into())))); @@ -476,13 +484,12 @@ fn get_receiver_trade_preimage() { paid_from_trading_vol: false, }; - let actual = coin - .get_receiver_trade_fee(FeeApproxStage::WithoutApprox) - .wait() - .expect("!get_sender_trade_fee"); + let actual = + block_on_f01(coin.get_receiver_trade_fee(FeeApproxStage::WithoutApprox)).expect("!get_sender_trade_fee"); assert_eq!(actual, expected_fee); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn test_get_fee_to_send_taker_fee() { const DEX_FEE_AMOUNT: u64 = 100_000; @@ -533,6 +540,7 @@ fn test_get_fee_to_send_taker_fee() { /// /// Please note this test doesn't work correctly now, /// because as of now [`EthCoin::get_fee_to_send_taker_fee`] doesn't process the `Exception` web3 error correctly. +#[cfg(not(target_arch = "wasm32"))] #[test] #[ignore] fn test_get_fee_to_send_taker_fee_insufficient_balance() { @@ -562,6 +570,7 @@ fn test_get_fee_to_send_taker_fee_insufficient_balance() { ); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn validate_dex_fee_invalid_sender_eth() { let (_ctx, coin) = eth_coin_for_test(EthCoinType::Eth, &[ETH_MAINNET_NODE], None, ETH_MAINNET_CHAIN_ID); @@ -582,13 +591,14 @@ fn validate_dex_fee_invalid_sender_eth() { min_block_number: 0, uuid: &[], }; - let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let error = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("was sent from wrong address")), _ => panic!("Expected `WrongPaymentTx` wrong sender address, found {:?}", error), } } +#[cfg(not(target_arch = "wasm32"))] #[test] fn validate_dex_fee_invalid_sender_erc() { let (_ctx, coin) = eth_coin_for_test( @@ -617,13 +627,14 @@ fn validate_dex_fee_invalid_sender_erc() { min_block_number: 0, uuid: &[], }; - let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let error = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("was sent from wrong address")), _ => panic!("Expected `WrongPaymentTx` wrong sender address, found {:?}", error), } } +#[cfg(not(target_arch = "wasm32"))] fn sender_compressed_pub(tx: &SignedEthTx) -> [u8; 33] { let tx_pubkey = tx.public.unwrap(); let mut raw_pubkey = [0; 65]; @@ -633,6 +644,7 @@ fn sender_compressed_pub(tx: &SignedEthTx) -> [u8; 33] { secp_public.serialize() } +#[cfg(not(target_arch = "wasm32"))] #[test] fn validate_dex_fee_eth_confirmed_before_min_block() { let (_ctx, coin) = eth_coin_for_test(EthCoinType::Eth, &[ETH_MAINNET_NODE], None, ETH_MAINNET_CHAIN_ID); @@ -655,13 +667,14 @@ fn validate_dex_fee_eth_confirmed_before_min_block() { min_block_number: 11784793, uuid: &[], }; - let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let error = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), _ => panic!("Expected `WrongPaymentTx` early confirmation, found {:?}", error), } } +#[cfg(not(target_arch = "wasm32"))] #[test] fn validate_dex_fee_erc_confirmed_before_min_block() { let (_ctx, coin) = eth_coin_for_test( @@ -693,13 +706,14 @@ fn validate_dex_fee_erc_confirmed_before_min_block() { min_block_number: 11823975, uuid: &[], }; - let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let error = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), _ => panic!("Expected `WrongPaymentTx` early confirmation, found {:?}", error), } } +#[cfg(not(target_arch = "wasm32"))] #[test] fn test_negotiate_swap_contract_addr_no_fallback() { let (_, coin) = eth_coin_for_test(EthCoinType::Eth, &[ETH_MAINNET_NODE], None, ETH_MAINNET_CHAIN_ID); @@ -727,6 +741,7 @@ fn test_negotiate_swap_contract_addr_no_fallback() { assert_eq!(Some(slice.to_vec().into()), result); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn test_negotiate_swap_contract_addr_has_fallback() { let fallback = Address::from_str("0x8500AFc0bc5214728082163326C2FF0C73f4a871").unwrap(); @@ -815,15 +830,14 @@ fn polygon_check_if_my_payment_sent() { amount: &BigDecimal::default(), payment_instructions: &None, }; - let my_payment = coin - .check_if_my_payment_sent(if_my_payment_sent_args) - .wait() + let my_payment = block_on(coin.check_if_my_payment_sent(if_my_payment_sent_args)) .unwrap() .unwrap(); let expected_hash = BytesJson::from("69a20008cea0c15ee483b5bbdff942752634aa072dfd2ff715fe87eec302de11"); assert_eq!(expected_hash, my_payment.tx_hash_as_bytes()); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn test_message_hash() { let key_pair = Random.generate().unwrap(); @@ -842,6 +856,7 @@ fn test_message_hash() { ); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn test_sign_verify_message() { let key_pair = KeyPair::from_secret_slice( @@ -866,6 +881,7 @@ fn test_sign_verify_message() { assert!(is_valid); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn test_eth_extract_secret() { let key_pair = Random.generate().unwrap(); @@ -978,3 +994,56 @@ fn test_fee_history() { let res = block_on(coin.eth_fee_history(U256::from(1u64), BlockNumber::Latest, &[])); assert!(res.is_ok()); } + +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_gas_limit_conf() { + use mm2_test_helpers::for_tests::ETH_SEPOLIA_SWAP_CONTRACT; + + let conf = json!({ + "coins": [{ + "coin": "ETH", + "name": "ethereum", + "fname": "Ethereum", + "chain_id": 1337, + "protocol":{ + "type": "ETH" + }, + "chain_id": 1, + "rpcport": 80, + "mm2": 1, + "gas_limit": { + "erc20_payment": 120000, + "erc20_receiver_spend": 130000, + "erc20_sender_refund": 110000 + } + }] + }); + + let ctx = MmCtxBuilder::new().with_conf(conf).into_mm_arc(); + CryptoCtx::init_with_iguana_passphrase(ctx.clone(), "123456").unwrap(); + + let req = json!({ + "urls":ETH_SEPOLIA_NODES, + "swap_contract_address":ETH_SEPOLIA_SWAP_CONTRACT + }); + let coin = block_on(lp_coininit(&ctx, "ETH", &req)).unwrap(); + let eth_coin = match coin { + MmCoinEnum::EthCoin(eth_coin) => eth_coin, + _ => panic!("not eth coin"), + }; + assert!( + eth_coin.gas_limit.eth_send_coins == 21_000 + && eth_coin.gas_limit.erc20_payment == 120000 + && eth_coin.gas_limit.erc20_receiver_spend == 130000 + && eth_coin.gas_limit.erc20_sender_refund == 110000 + && eth_coin.gas_limit.eth_max_trade_gas == 150_000 + ); +} + +#[test] +fn test_h256_to_str() { + let h = H256::from_str("5136701f11060010841c9708c3eb26f6606a070b8ae43f4b98b6d7b10a545258").unwrap(); + let b: BytesJson = h.0.to_vec().into(); + println!("H256={}", format!("0x{:02x}", b)); +} diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index a36e0ac45a..287b3fb79a 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -40,7 +40,7 @@ async fn init_eth_coin_helper() -> Result<(MmArc, MmCoinEnum), String> { let req = json!({ "urls":ETH_SEPOLIA_NODES, - "swap_contract_address":ETH_SEPOLIA_SWAP_CONTRACT + "swap_contract_address":ETH_SEPOLIA_SWAP_CONTRACT, }); Ok((ctx.clone(), lp_coininit(&ctx, "ETH", &req).await?)) } diff --git a/mm2src/coins/eth/for_tests.rs b/mm2src/coins/eth/for_tests.rs index 9ea93188ea..cc6d5cd375 100644 --- a/mm2src/coins/eth/for_tests.rs +++ b/mm2src/coins/eth/for_tests.rs @@ -33,7 +33,7 @@ pub(crate) fn eth_coin_from_keypair( for url in urls.iter() { let node = HttpTransportNode { uri: url.parse().unwrap(), - gui_auth: false, + komodo_proxy: false, }; let transport = Web3Transport::new_http(node); let web3 = Web3::new(transport); @@ -49,6 +49,9 @@ pub(crate) fn eth_coin_from_keypair( EthCoinType::Nft { ref platform } => platform.to_string(), }; let my_address = key_pair.address(); + let coin_conf = coin_conf(&ctx, &ticker); + let gas_limit: EthGasLimit = extract_gas_limit_from_conf(&coin_conf).expect("expected valid gas_limit config"); + let gas_limit_v2: EthGasLimitV2 = extract_gas_limit_from_conf(&coin_conf).expect("expected valid gas_limit config"); let eth_coin = EthCoin(Arc::new(EthCoinImpl { coin_type, @@ -58,6 +61,7 @@ pub(crate) fn eth_coin_from_keypair( priv_key_policy: key_pair.into(), derivation_method: Arc::new(DerivationMethod::SingleAddress(my_address)), swap_contract_address: Address::from_str(ETH_SEPOLIA_SWAP_CONTRACT).unwrap(), + swap_v2_contracts: None, fallback_swap_contract, contract_supports_watchers: false, ticker, @@ -73,6 +77,8 @@ pub(crate) fn eth_coin_from_keypair( erc20_tokens_infos: Default::default(), nfts_infos: Arc::new(Default::default()), platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), + gas_limit, + gas_limit_v2, abortable_system: AbortableQueue::default(), })); (ctx, eth_coin) diff --git a/mm2src/coins/eth/maker_swap_v2_abi.json b/mm2src/coins/eth/maker_swap_v2_abi.json new file mode 100644 index 0000000000..74e5da3e80 --- /dev/null +++ b/mm2src/coins/eth/maker_swap_v2_abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"MakerPaymentRefundedSecret","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"MakerPaymentRefundedTimelock","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"MakerPaymentSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"MakerPaymentSpent","type":"event"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"uint32","name":"paymentLockTime","type":"uint32"}],"name":"erc20MakerPayment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"uint32","name":"paymentLockTime","type":"uint32"}],"name":"ethMakerPayment","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"makerPayments","outputs":[{"internalType":"bytes20","name":"paymentHash","type":"bytes20"},{"internalType":"uint32","name":"paymentLockTime","type":"uint32"},{"internalType":"enum EtomicSwapMakerV2.MakerPaymentState","name":"state","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"bytes32","name":"takerSecret","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"refundMakerPaymentSecret","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"refundMakerPaymentTimelock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecret","type":"bytes32"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"spendMakerPayment","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/mm2src/coins/eth/nft_maker_swap_v2_abi.json b/mm2src/coins/eth/nft_maker_swap_v2_abi.json new file mode 100644 index 0000000000..95def23766 --- /dev/null +++ b/mm2src/coins/eth/nft_maker_swap_v2_abi.json @@ -0,0 +1,462 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "MakerPaymentRefundedSecret", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "MakerPaymentRefundedTimelock", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "MakerPaymentSent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "MakerPaymentSpent", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "makerPayments", + "outputs": [ + { + "internalType": "bytes20", + "name": "paymentHash", + "type": "bytes20" + }, + { + "internalType": "uint32", + "name": "paymentLockTime", + "type": "uint32" + }, + { + "internalType": "enum EtomicSwapMakerNftV2.MakerPaymentState", + "name": "state", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155BatchReceived", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "onERC1155Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "onERC721Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "taker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecret", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecretHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "refundErc1155MakerPaymentSecret", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "taker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecretHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecretHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "refundErc1155MakerPaymentTimelock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "taker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecret", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecretHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "refundErc721MakerPaymentSecret", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "taker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecretHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecretHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "refundErc721MakerPaymentTimelock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecretHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "spendErc1155MakerPayment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecretHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "spendErc721MakerPayment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/mm2src/coins/eth/nft_swap_v2/errors.rs b/mm2src/coins/eth/nft_swap_v2/errors.rs index e66cd437d0..02cb3c7626 100644 --- a/mm2src/coins/eth/nft_swap_v2/errors.rs +++ b/mm2src/coins/eth/nft_swap_v2/errors.rs @@ -1,41 +1,27 @@ +pub(crate) use crate::eth::eth_swap_v2::PrepareTxDataError; use enum_derives::EnumFromStringify; #[derive(Debug, Display)] pub(crate) enum Erc721FunctionError { - AbiError(String), + #[display(fmt = "ABI error: {}", _0)] + ABIError(String), FunctionNotFound(String), } -#[derive(Debug, Display)] +#[derive(Debug, Display, EnumFromStringify)] pub(crate) enum HtlcParamsError { WrongPaymentTx(String), - TxDeserializationError(String), -} - -#[derive(Debug, Display, EnumFromStringify)] -pub(crate) enum PaymentStatusErr { - #[from_stringify("ethabi::Error")] - #[display(fmt = "Abi error: {}", _0)] - AbiError(String), - #[from_stringify("web3::Error")] - #[display(fmt = "Transport error: {}", _0)] - Transport(String), - #[display(fmt = "Internal error: {}", _0)] - Internal(String), - #[display(fmt = "Tx deserialization error: {}", _0)] - TxDeserializationError(String), -} - -#[derive(Debug, Display, EnumFromStringify)] -pub(crate) enum PrepareTxDataError { #[from_stringify("ethabi::Error")] - #[display(fmt = "Abi error: {}", _0)] - AbiError(String), - #[display(fmt = "Internal error: {}", _0)] - Internal(String), - Erc721FunctionError(Erc721FunctionError), + #[display(fmt = "ABI error: {}", _0)] + ABIError(String), + InvalidData(String), } impl From for PrepareTxDataError { - fn from(e: Erc721FunctionError) -> Self { Self::Erc721FunctionError(e) } + fn from(e: Erc721FunctionError) -> Self { + match e { + Erc721FunctionError::ABIError(e) => PrepareTxDataError::ABIError(e), + Erc721FunctionError::FunctionNotFound(e) => PrepareTxDataError::Internal(e), + } + } } diff --git a/mm2src/coins/eth/nft_swap_v2/mod.rs b/mm2src/coins/eth/nft_swap_v2/mod.rs index a446efde20..f4c909bd32 100644 --- a/mm2src/coins/eth/nft_swap_v2/mod.rs +++ b/mm2src/coins/eth/nft_swap_v2/mod.rs @@ -1,52 +1,58 @@ -use crate::coin_errors::{ValidatePaymentError, ValidatePaymentResult}; use ethabi::{Contract, Token}; -use ethcore_transaction::{Action, UnverifiedTransactionWrapper}; +use ethcore_transaction::Action; use ethereum_types::{Address, U256}; +use ethkey::public_to_address; use futures::compat::Future01CompatExt; use mm2_err_handle::prelude::{MapToMmResult, MmError, MmResult}; use mm2_number::BigDecimal; -use std::convert::TryInto; -use web3::types::{Transaction as Web3Tx, TransactionId}; +use num_traits::Signed; +use web3::types::TransactionId; + +use super::ContractType; +use crate::coin_errors::{ValidatePaymentError, ValidatePaymentResult}; +use crate::eth::eth_swap_v2::{validate_from_to_and_status, validate_payment_state, EthPaymentType, PaymentMethod, + PaymentStatusErr, PrepareTxDataError, ZERO_VALUE}; +use crate::eth::{decode_contract_call, EthCoin, EthCoinType, MakerPaymentStateV2, SignedEthTx, ERC1155_CONTRACT, + ERC721_CONTRACT, NFT_MAKER_SWAP_V2}; +use crate::{ParseCoinAssocTypes, RefundNftMakerPaymentArgs, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, + TransactionErr, ValidateNftMakerPaymentArgs}; pub(crate) mod errors; -use errors::{Erc721FunctionError, HtlcParamsError, PaymentStatusErr, PrepareTxDataError}; +use errors::{Erc721FunctionError, HtlcParamsError}; mod structs; -use structs::{ExpectedHtlcParams, PaymentType, ValidationParams}; - -use super::ContractType; -use crate::eth::{addr_from_raw_pubkey, decode_contract_call, gas_limit::ETH_MAX_TRADE_GAS, EthCoin, EthCoinType, - MakerPaymentStateV2, SignedEthTx, TryToAddress, ERC1155_CONTRACT, ERC721_CONTRACT, NFT_SWAP_CONTRACT}; -use crate::{ParseCoinAssocTypes, RefundPaymentArgs, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, TransactionErr, - ValidateNftMakerPaymentArgs}; +use structs::{ExpectedHtlcParams, ValidationParams}; impl EthCoin { pub(crate) async fn send_nft_maker_payment_v2_impl( &self, args: SendNftMakerPaymentArgs<'_, Self>, ) -> Result { - try_tx_s!(validate_payment_args( - args.taker_secret_hash, - args.maker_secret_hash, - &args.amount, - args.nft_swap_info.contract_type - )); - let htlc_data = try_tx_s!(self.prepare_htlc_data(&args)); - match &self.coin_type { EthCoinType::Nft { .. } => { + try_tx_s!(validate_payment_args( + args.taker_secret_hash, + args.maker_secret_hash, + &args.amount, + args.nft_swap_info.contract_type + )); + let htlc_data = try_tx_s!(self.prepare_htlc_data(&args)); + let data = try_tx_s!(self.prepare_nft_maker_payment_v2_data(&args, htlc_data).await); + let gas_limit = self + .gas_limit_v2 + .nft_gas_limit(args.nft_swap_info.contract_type, PaymentMethod::Send); self.sign_and_send_transaction( - 0.into(), + ZERO_VALUE.into(), Action::Call(*args.nft_swap_info.token_address), data, - U256::from(ETH_MAX_TRADE_GAS), // TODO: fix to a more accurate const or estimated value + U256::from(gas_limit), ) .compat() .await }, - EthCoinType::Eth | EthCoinType::Erc20 { .. } => Err(TransactionErr::ProtocolNotSupported( - "ETH and ERC20 Protocols are not supported for NFT Swaps".to_string(), - )), + EthCoinType::Eth | EthCoinType::Erc20 { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( + "ETH and ERC20 protocols are not supported for NFT swaps." + ))), } } @@ -54,43 +60,54 @@ impl EthCoin { &self, args: ValidateNftMakerPaymentArgs<'_, Self>, ) -> ValidatePaymentResult<()> { - let contract_type = args.nft_swap_info.contract_type; - validate_payment_args( - args.taker_secret_hash, - args.maker_secret_hash, - &args.amount, - contract_type, - ) - .map_err(ValidatePaymentError::InternalError)?; - let etomic_swap_contract = args.nft_swap_info.swap_contract_address; - let token_address = args.nft_swap_info.token_address; - let maker_address = addr_from_raw_pubkey(args.maker_pub).map_to_mm(ValidatePaymentError::InternalError)?; - let time_lock_u32 = args - .time_lock - .try_into() - .map_err(ValidatePaymentError::TimelockOverflow)?; - let swap_id = self.etomic_swap_id(time_lock_u32, args.maker_secret_hash); - let maker_status = self - .payment_status_v2( - *etomic_swap_contract, - Token::FixedBytes(swap_id.clone()), - &NFT_SWAP_CONTRACT, - PaymentType::MakerPayments, - ) - .await?; - let tx_from_rpc = self - .transaction(TransactionId::Hash(args.maker_payment_tx.tx_hash())) - .await?; - let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { - ValidatePaymentError::TxDoesNotExist(format!( - "Didn't find provided tx {:?} on ETH node", - args.maker_payment_tx.tx_hash() - )) - })?; - validate_from_to_and_maker_status(tx_from_rpc, maker_address, *token_address, maker_status).await?; match self.coin_type { EthCoinType::Nft { .. } => { - let (decoded, index_bytes) = get_decoded_tx_data_and_index_bytes(contract_type, &tx_from_rpc.input.0)?; + let nft_maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + ValidatePaymentError::InternalError( + "Expected swap_v2_contracts to be Some, but found None".to_string(), + ) + })? + .nft_maker_swap_v2_contract; + let contract_type = args.nft_swap_info.contract_type; + validate_payment_args( + args.taker_secret_hash, + args.maker_secret_hash, + &args.amount, + contract_type, + ) + .map_err(ValidatePaymentError::InternalError)?; + let token_address = args.nft_swap_info.token_address; + let maker_address = public_to_address(args.maker_pub); + let swap_id = self.etomic_swap_id_v2(args.time_lock, args.maker_secret_hash); + let maker_status = self + .payment_status_v2( + nft_maker_swap_v2_contract, + Token::FixedBytes(swap_id.clone()), + &NFT_MAKER_SWAP_V2, + EthPaymentType::MakerPayments, + 2, + ) + .await?; + let tx_from_rpc = self + .transaction(TransactionId::Hash(args.maker_payment_tx.tx_hash())) + .await?; + let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { + ValidatePaymentError::TxDoesNotExist(format!( + "Didn't find provided tx {:?} on ETH node", + args.maker_payment_tx.tx_hash() + )) + })?; + validate_from_to_and_status( + tx_from_rpc, + maker_address, + *token_address, + maker_status, + MakerPaymentStateV2::PaymentSent as u8, + )?; + + let (decoded, bytes_index) = get_decoded_tx_data_and_bytes_index(contract_type, &tx_from_rpc.input.0)?; let amount = if matches!(contract_type, &ContractType::Erc1155) { Some(args.amount.to_string()) @@ -100,14 +117,13 @@ impl EthCoin { let validation_params = ValidationParams { maker_address, - etomic_swap_contract: *etomic_swap_contract, + nft_maker_swap_v2_contract, token_id: args.nft_swap_info.token_id, amount, }; validate_decoded_data(&decoded, &validation_params)?; - let taker_address = - addr_from_raw_pubkey(args.taker_pub).map_to_mm(ValidatePaymentError::InternalError)?; + let taker_address = public_to_address(args.taker_pub); let htlc_params = ExpectedHtlcParams { swap_id, taker_address, @@ -116,7 +132,7 @@ impl EthCoin { maker_secret_hash: args.maker_secret_hash.to_vec(), time_lock: U256::from(args.time_lock), }; - decode_and_validate_htlc_params(decoded, index_bytes, htlc_params)?; + decode_and_validate_htlc_params(decoded, bytes_index, htlc_params)?; }, EthCoinType::Eth | EthCoinType::Erc20 { .. } => { return MmError::err(ValidatePaymentError::InternalError( @@ -131,52 +147,147 @@ impl EthCoin { &self, args: SpendNftMakerPaymentArgs<'_, Self>, ) -> Result { - let etomic_swap_contract = args.swap_contract_address; - if args.maker_secret.len() != 32 { - return Err(TransactionErr::Plain(ERRL!("maker_secret must be 32 bytes"))); - } - let contract_type = args.contract_type; - let (decoded, index_bytes) = try_tx_s!(get_decoded_tx_data_and_index_bytes( - contract_type, - args.maker_payment_tx.unsigned().data() - )); - - let (state, htlc_params) = try_tx_s!( - self.status_and_htlc_params_from_tx_data( - *etomic_swap_contract, - &NFT_SWAP_CONTRACT, - &decoded, - index_bytes, - PaymentType::MakerPayments, - ) - .await - ); match self.coin_type { EthCoinType::Nft { .. } => { + let nft_maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")) + })? + .nft_maker_swap_v2_contract; + if args.maker_secret.len() != 32 { + return Err(TransactionErr::Plain(ERRL!("maker_secret must be 32 bytes"))); + } + let (decoded, bytes_index) = try_tx_s!(get_decoded_tx_data_and_bytes_index( + args.contract_type, + args.maker_payment_tx.unsigned().data() + )); + + let (state, htlc_params) = try_tx_s!( + self.status_and_htlc_params_from_tx_data( + nft_maker_swap_v2_contract, + &NFT_MAKER_SWAP_V2, + &decoded, + bytes_index, + EthPaymentType::MakerPayments, + 2 + ) + .await + ); let data = try_tx_s!(self.prepare_spend_nft_maker_v2_data(&args, decoded, htlc_params, state)); + let gas_limit = self + .gas_limit_v2 + .nft_gas_limit(args.contract_type, PaymentMethod::Spend); self.sign_and_send_transaction( - 0.into(), - Action::Call(*etomic_swap_contract), + ZERO_VALUE.into(), + Action::Call(nft_maker_swap_v2_contract), data, - U256::from(ETH_MAX_TRADE_GAS), // TODO: fix to a more accurate const or estimated value + U256::from(gas_limit), ) .compat() .await }, - EthCoinType::Eth | EthCoinType::Erc20 { .. } => Err(TransactionErr::ProtocolNotSupported( - "ETH and ERC20 Protocols are not supported for NFT Swaps".to_string(), - )), + EthCoinType::Eth | EthCoinType::Erc20 { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( + "ETH and ERC20 protocols are not supported for NFT swaps." + ))), } } pub(crate) async fn refund_nft_maker_payment_v2_timelock_impl( &self, - args: RefundPaymentArgs<'_>, + args: RefundNftMakerPaymentArgs<'_, Self>, ) -> Result { - let _etomic_swap_contract = try_tx_s!(args.swap_contract_address.try_to_address()); - let tx: UnverifiedTransactionWrapper = try_tx_s!(rlp::decode(args.payment_tx)); - let _payment = try_tx_s!(SignedEthTx::new(tx)); - todo!() + match self.coin_type { + EthCoinType::Nft { .. } => { + let nft_maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")) + })? + .nft_maker_swap_v2_contract; + let (decoded, bytes_index) = try_tx_s!(get_decoded_tx_data_and_bytes_index( + args.contract_type, + args.maker_payment_tx.unsigned().data() + )); + + let (state, htlc_params) = try_tx_s!( + self.status_and_htlc_params_from_tx_data( + nft_maker_swap_v2_contract, + &NFT_MAKER_SWAP_V2, + &decoded, + bytes_index, + EthPaymentType::MakerPayments, + 2 + ) + .await + ); + let data = + try_tx_s!(self.prepare_refund_nft_maker_payment_v2_timelock(&args, decoded, htlc_params, state)); + let gas_limit = self + .gas_limit_v2 + .nft_gas_limit(args.contract_type, PaymentMethod::RefundTimelock); + self.sign_and_send_transaction( + ZERO_VALUE.into(), + Action::Call(nft_maker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await + }, + EthCoinType::Eth | EthCoinType::Erc20 { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( + "ETH and ERC20 protocols are not supported for NFT swaps." + ))), + } + } + + pub(crate) async fn refund_nft_maker_payment_v2_secret_impl( + &self, + args: RefundNftMakerPaymentArgs<'_, Self>, + ) -> Result { + match self.coin_type { + EthCoinType::Nft { .. } => { + let nft_maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")) + })? + .nft_maker_swap_v2_contract; + let (decoded, bytes_index) = try_tx_s!(get_decoded_tx_data_and_bytes_index( + args.contract_type, + args.maker_payment_tx.unsigned().data() + )); + + let (state, htlc_params) = try_tx_s!( + self.status_and_htlc_params_from_tx_data( + nft_maker_swap_v2_contract, + &NFT_MAKER_SWAP_V2, + &decoded, + bytes_index, + EthPaymentType::MakerPayments, + 2 + ) + .await + ); + + let data = + try_tx_s!(self.prepare_refund_nft_maker_payment_v2_secret(&args, decoded, htlc_params, state)); + let gas_limit = self + .gas_limit_v2 + .nft_gas_limit(args.contract_type, PaymentMethod::RefundSecret); + self.sign_and_send_transaction( + ZERO_VALUE.into(), + Action::Call(nft_maker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await + }, + EthCoinType::Eth | EthCoinType::Erc20 { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( + "ETH and ERC20 protocols are not supported for NFT swaps." + ))), + } } async fn prepare_nft_maker_payment_v2_data( @@ -184,6 +295,12 @@ impl EthCoin { args: &SendNftMakerPaymentArgs<'_, Self>, htlc_data: Vec, ) -> Result, PrepareTxDataError> { + let nft_maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + PrepareTxDataError::Internal("Expected swap_v2_contracts to be Some, but found None".to_string()) + })? + .nft_maker_swap_v2_contract; match args.nft_swap_info.contract_type { ContractType::Erc1155 => { let function = ERC1155_CONTRACT.function("safeTransferFrom")?; @@ -191,7 +308,7 @@ impl EthCoin { .map_err(|e| PrepareTxDataError::Internal(e.to_string()))?; let data = function.encode_input(&[ Token::Address(self.my_addr().await), - Token::Address(*args.nft_swap_info.swap_contract_address), + Token::Address(nft_maker_swap_v2_contract), Token::Uint(U256::from(args.nft_swap_info.token_id)), Token::Uint(amount_u256), Token::Bytes(htlc_data), @@ -202,7 +319,7 @@ impl EthCoin { let function = erc721_transfer_with_data()?; let data = function.encode_input(&[ Token::Address(self.my_addr().await), - Token::Address(*args.nft_swap_info.swap_contract_address), + Token::Address(nft_maker_swap_v2_contract), Token::Uint(U256::from(args.nft_swap_info.token_id)), Token::Bytes(htlc_data), ])?; @@ -212,51 +329,19 @@ impl EthCoin { } fn prepare_htlc_data(&self, args: &SendNftMakerPaymentArgs<'_, Self>) -> Result, PrepareTxDataError> { - let taker_address = - addr_from_raw_pubkey(args.taker_pub).map_err(|e| PrepareTxDataError::Internal(ERRL!("{}", e)))?; - let time_lock_u32 = args - .time_lock - .try_into() - .map_err(|e| PrepareTxDataError::Internal(ERRL!("{}", e)))?; - let id = self.etomic_swap_id(time_lock_u32, args.maker_secret_hash); + let taker_address = public_to_address(args.taker_pub); + let id = self.etomic_swap_id_v2(args.time_lock, args.maker_secret_hash); let encoded = ethabi::encode(&[ Token::FixedBytes(id), Token::Address(taker_address), Token::Address(*args.nft_swap_info.token_address), Token::FixedBytes(args.taker_secret_hash.to_vec()), Token::FixedBytes(args.maker_secret_hash.to_vec()), - Token::Uint(U256::from(time_lock_u32)), + Token::Uint(U256::from(args.time_lock)), ]); Ok(encoded) } - /// Retrieves the payment status from a given smart contract address based on the swap ID and state type. - async fn payment_status_v2( - &self, - swap_address: Address, - swap_id: Token, - contract_abi: &Contract, - state_type: PaymentType, - ) -> Result { - let function_name = state_type.as_str(); - let function = contract_abi.function(function_name)?; - let data = function.encode_input(&[swap_id])?; - let bytes = self - .call_request(self.my_addr().await, swap_address, None, Some(data.into())) - .await?; - let decoded_tokens = function.decode_output(&bytes.0)?; - let state = decoded_tokens - .get(2) - .ok_or_else(|| PaymentStatusErr::Internal(ERRL!("Payment status must contain 'state' as the 2nd token")))?; - match state { - Token::Uint(state) => Ok(*state), - _ => Err(PaymentStatusErr::Internal(ERRL!( - "Payment status must be Uint, got {:?}", - state - ))), - } - } - /// Prepares the encoded transaction data for spending a maker's NFT payment on the blockchain. /// /// This function selects the appropriate contract function based on the NFT's contract type (ERC1155 or ERC721) @@ -268,40 +353,88 @@ impl EthCoin { htlc_params: Vec, state: U256, ) -> Result, PrepareTxDataError> { + validate_payment_state(args.maker_payment_tx, state, MakerPaymentStateV2::PaymentSent as u8)?; + let spend_func = match args.contract_type { - ContractType::Erc1155 => NFT_SWAP_CONTRACT.function("spendErc1155MakerPayment")?, - ContractType::Erc721 => NFT_SWAP_CONTRACT.function("spendErc721MakerPayment")?, + ContractType::Erc1155 => NFT_MAKER_SWAP_V2.function("spendErc1155MakerPayment")?, + ContractType::Erc721 => NFT_MAKER_SWAP_V2.function("spendErc721MakerPayment")?, }; - - if state != U256::from(MakerPaymentStateV2::PaymentSent as u8) { - return Err(PrepareTxDataError::Internal(ERRL!( - "Payment {:?} state is not PAYMENT_STATE_SENT, got {}", - args.maker_payment_tx, - state - ))); + // Initialize tokens with common elements + let mut input_tokens = vec![ + htlc_params[0].clone(), // swapId + Token::Address(args.maker_payment_tx.sender()), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret.to_vec()), + htlc_params[2].clone(), // tokenAddress + decoded[2].clone(), // tokenId + ]; + // Add specific elements based on contract type + if let ContractType::Erc1155 = args.contract_type { + input_tokens.push(decoded[3].clone()); // amount } - let input_tokens = match args.contract_type { - ContractType::Erc1155 => vec![ - htlc_params[0].clone(), // swap_id - Token::Address(args.maker_payment_tx.sender()), - Token::FixedBytes(args.taker_secret_hash.to_vec()), - Token::FixedBytes(args.maker_secret.to_vec()), - htlc_params[2].clone(), // tokenAddress - decoded[2].clone(), // tokenId - decoded[3].clone(), // amount - ], - ContractType::Erc721 => vec![ - htlc_params[0].clone(), // swap_id - Token::Address(args.maker_payment_tx.sender()), - Token::FixedBytes(args.taker_secret_hash.to_vec()), - Token::FixedBytes(args.maker_secret.to_vec()), - htlc_params[2].clone(), // tokenAddress - decoded[2].clone(), // tokenId - ], + let data = spend_func.encode_input(&input_tokens)?; + Ok(data) + } + + fn prepare_refund_nft_maker_payment_v2_timelock( + &self, + args: &RefundNftMakerPaymentArgs<'_, Self>, + decoded: Vec, + htlc_params: Vec, + state: U256, + ) -> Result, PrepareTxDataError> { + validate_payment_state(args.maker_payment_tx, state, MakerPaymentStateV2::PaymentSent as u8)?; + + let refund_func = match args.contract_type { + ContractType::Erc1155 => NFT_MAKER_SWAP_V2.function("refundErc1155MakerPaymentTimelock")?, + ContractType::Erc721 => NFT_MAKER_SWAP_V2.function("refundErc721MakerPaymentTimelock")?, }; + // Initialize tokens with common elements + let mut input_tokens = vec![ + htlc_params[0].clone(), // swapId + htlc_params[1].clone(), // takerAddress + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + htlc_params[2].clone(), // tokenAddress + decoded[2].clone(), // tokenId + ]; + // Add specific elements based on contract type + if let ContractType::Erc1155 = args.contract_type { + input_tokens.push(decoded[3].clone()); // amount + } - let data = spend_func.encode_input(&input_tokens)?; + let data = refund_func.encode_input(&input_tokens)?; + Ok(data) + } + + fn prepare_refund_nft_maker_payment_v2_secret( + &self, + args: &RefundNftMakerPaymentArgs<'_, Self>, + decoded: Vec, + htlc_params: Vec, + state: U256, + ) -> Result, PrepareTxDataError> { + validate_payment_state(args.maker_payment_tx, state, MakerPaymentStateV2::PaymentSent as u8)?; + + let refund_func = match args.contract_type { + ContractType::Erc1155 => NFT_MAKER_SWAP_V2.function("refundErc1155MakerPaymentSecret")?, + ContractType::Erc721 => NFT_MAKER_SWAP_V2.function("refundErc721MakerPaymentSecret")?, + }; + // Initialize tokens with common elements + let mut input_tokens = vec![ + htlc_params[0].clone(), // swapId + htlc_params[1].clone(), // takerAddress + Token::FixedBytes(args.taker_secret.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + htlc_params[2].clone(), // tokenAddress + decoded[2].clone(), // tokenId + ]; + // Add specific elements based on contract type + if let ContractType::Erc1155 = args.contract_type { + input_tokens.push(decoded[3].clone()); // amount + } + let data = refund_func.encode_input(&input_tokens)?; Ok(data) } @@ -311,28 +444,30 @@ impl EthCoin { contract_abi: &Contract, decoded_data: &[Token], index: usize, - state_type: PaymentType, + payment_type: EthPaymentType, + state_index: usize, ) -> Result<(U256, Vec), PaymentStatusErr> { let data_bytes = match decoded_data.get(index) { Some(Token::Bytes(data_bytes)) => data_bytes, _ => { - return Err(PaymentStatusErr::TxDeserializationError(ERRL!( + return Err(PaymentStatusErr::InvalidData(ERRL!( "Failed to decode HTLCParams from data_bytes" ))) }, }; - let htlc_params = match ethabi::decode(htlc_params(), data_bytes) { - Ok(htlc_params) => htlc_params, - Err(_) => { - return Err(PaymentStatusErr::TxDeserializationError(ERRL!( - "Failed to decode HTLCParams from data_bytes" - ))) - }, - }; + let htlc_params = + ethabi::decode(htlc_params(), data_bytes).map_err(|e| PaymentStatusErr::ABIError(ERRL!("{}", e)))?; let state = self - .payment_status_v2(swap_address, htlc_params[0].clone(), contract_abi, state_type) + .payment_status_v2( + swap_address, + // swap_id has 0 index + htlc_params[0].clone(), + contract_abi, + payment_type, + state_index, + ) .await?; Ok((state, htlc_params)) @@ -343,7 +478,11 @@ impl EthCoin { fn validate_decoded_data(decoded: &[Token], params: &ValidationParams) -> Result<(), MmError> { let checks = vec![ (0, Token::Address(params.maker_address), "maker_address"), - (1, Token::Address(params.etomic_swap_contract), "etomic_swap_contract"), + ( + 1, + Token::Address(params.nft_maker_swap_v2_contract), + "nft_maker_swap_v2_contract", + ), (2, Token::Uint(U256::from(params.token_id)), "token_id"), ]; @@ -378,20 +517,13 @@ fn decode_and_validate_htlc_params( let data_bytes = match decoded.get(index) { Some(Token::Bytes(bytes)) => bytes, _ => { - return MmError::err(HtlcParamsError::TxDeserializationError( + return MmError::err(HtlcParamsError::InvalidData( "Expected Bytes for HTLCParams data".to_string(), )) }, }; - let decoded_params = match ethabi::decode(htlc_params(), data_bytes) { - Ok(params) => params, - Err(_) => { - return MmError::err(HtlcParamsError::TxDeserializationError( - "Failed to decode HTLCParams from data_bytes".to_string(), - )) - }, - }; + let decoded_params = ethabi::decode(htlc_params(), data_bytes)?; let expected_taker_secret_hash = Token::FixedBytes(expected_params.taker_secret_hash.clone()); let expected_maker_secret_hash = Token::FixedBytes(expected_params.maker_secret_hash.clone()); @@ -440,7 +572,7 @@ fn htlc_params() -> &'static [ethabi::ParamType] { /// function to check if BigDecimal is a positive integer #[inline(always)] -fn is_positive_integer(amount: &BigDecimal) -> bool { amount == &amount.with_scale(0) && amount > &BigDecimal::from(0) } +fn is_positive_integer(amount: &BigDecimal) -> bool { amount == &amount.with_scale(0) && amount.is_positive() } fn validate_payment_args<'a>( taker_secret_hash: &'a [u8], @@ -470,47 +602,19 @@ fn validate_payment_args<'a>( Ok(()) } -async fn validate_from_to_and_maker_status( - tx_from_rpc: &Web3Tx, - expected_from: Address, - expected_to: Address, - maker_status: U256, -) -> ValidatePaymentResult<()> { - if maker_status != U256::from(MakerPaymentStateV2::PaymentSent as u8) { - return MmError::err(ValidatePaymentError::UnexpectedPaymentState(format!( - "NFT Maker Payment state is not PAYMENT_STATE_SENT, got {}", - maker_status - ))); - } - if tx_from_rpc.from != Some(expected_from) { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "NFT Maker Payment tx {:?} was sent from wrong address, expected {:?}", - tx_from_rpc, expected_from - ))); - } - // As NFT owner calls "safeTransferFrom" directly, then in Transaction 'to' field we expect token_address - if tx_from_rpc.to != Some(expected_to) { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "NFT Maker Payment tx {:?} was sent to wrong address, expected {:?}", - tx_from_rpc, expected_to, - ))); - } - Ok(()) -} - -/// Identifies the correct "safeTransferFrom" function based on the contract type (either ERC1155 or ERC721) +/// Identifies the correct `"safeTransferFrom"` function based on the contract type (either ERC1155 or ERC721) /// and decodes the provided contract call bytes using the ABI of the identified function. Additionally, it returns /// the index position of the "bytes" field within the function's parameters. -pub(crate) fn get_decoded_tx_data_and_index_bytes( +pub(crate) fn get_decoded_tx_data_and_bytes_index( contract_type: &ContractType, contract_call_bytes: &[u8], ) -> Result<(Vec, usize), PrepareTxDataError> { - let (send_func, index_bytes) = match contract_type { + let (send_func, bytes_index) = match contract_type { ContractType::Erc1155 => (ERC1155_CONTRACT.function("safeTransferFrom")?, 4), ContractType::Erc721 => (erc721_transfer_with_data()?, 3), }; let decoded = decode_contract_call(send_func, contract_call_bytes)?; - Ok((decoded, index_bytes)) + Ok((decoded, bytes_index)) } /// ERC721 contract has overloaded versions of the `safeTransferFrom` function, @@ -520,7 +624,7 @@ pub(crate) fn get_decoded_tx_data_and_index_bytes( fn erc721_transfer_with_data<'a>() -> Result<&'a ethabi::Function, Erc721FunctionError> { let functions = ERC721_CONTRACT .functions_by_name("safeTransferFrom") - .map_err(|e| Erc721FunctionError::AbiError(ERRL!("{}", e)))?; + .map_err(|e| Erc721FunctionError::ABIError(ERRL!("{}", e)))?; // Find the correct function variant by inspecting the input parameters. let function = functions diff --git a/mm2src/coins/eth/nft_swap_v2/structs.rs b/mm2src/coins/eth/nft_swap_v2/structs.rs index a0c129bf4b..7bd4130d9f 100644 --- a/mm2src/coins/eth/nft_swap_v2/structs.rs +++ b/mm2src/coins/eth/nft_swap_v2/structs.rs @@ -11,23 +11,8 @@ pub(crate) struct ExpectedHtlcParams { pub(crate) struct ValidationParams<'a> { pub(crate) maker_address: Address, - pub(crate) etomic_swap_contract: Address, + pub(crate) nft_maker_swap_v2_contract: Address, pub(crate) token_id: &'a [u8], // Optional, as it's not needed for ERC721 pub(crate) amount: Option, } - -#[allow(dead_code)] -pub(crate) enum PaymentType { - MakerPayments, - TakerPayments, -} - -impl PaymentType { - pub(crate) fn as_str(&self) -> &'static str { - match self { - PaymentType::MakerPayments => "makerPayments", - PaymentType::TakerPayments => "takerPayments", - } - } -} diff --git a/mm2src/coins/eth/taker_swap_v2_abi.json b/mm2src/coins/eth/taker_swap_v2_abi.json new file mode 100644 index 0000000000..4f2c8b782c --- /dev/null +++ b/mm2src/coins/eth/taker_swap_v2_abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"feeAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"TakerPaymentApproved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"secret","type":"bytes32"}],"name":"TakerPaymentRefundedSecret","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"TakerPaymentRefundedTimelock","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"TakerPaymentSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"secret","type":"bytes32"}],"name":"TakerPaymentSpent","type":"event"},{"inputs":[],"name":"dexFeeAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"dexFee","type":"uint256"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"uint32","name":"preApproveLockTime","type":"uint32"},{"internalType":"uint32","name":"paymentLockTime","type":"uint32"}],"name":"erc20TakerPayment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"dexFee","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"uint32","name":"preApproveLockTime","type":"uint32"},{"internalType":"uint32","name":"paymentLockTime","type":"uint32"}],"name":"ethTakerPayment","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"dexFee","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"bytes32","name":"takerSecret","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"refundTakerPaymentSecret","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"dexFee","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"refundTakerPaymentTimelock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"dexFee","type":"uint256"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecret","type":"bytes32"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"spendTakerPayment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"dexFee","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"takerPaymentApprove","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"takerPayments","outputs":[{"internalType":"bytes20","name":"paymentHash","type":"bytes20"},{"internalType":"uint32","name":"preApproveLockTime","type":"uint32"},{"internalType":"uint32","name":"paymentLockTime","type":"uint32"},{"internalType":"enum EtomicSwapTakerV2.TakerPaymentState","name":"state","type":"uint8"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index e7b2e62b9a..cee2313ba2 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -1,4 +1,6 @@ use super::*; +use crate::eth::erc20::{get_enabled_erc20_by_contract, get_token_decimals}; +use crate::eth::web3_transport::http_transport::HttpTransport; use crate::hd_wallet::{load_hd_accounts_from_storage, HDAccountsMutex, HDPathAccountToAddressId, HDWalletCoinStorage, HDWalletStorageError, DEFAULT_GAP_LIMIT}; use crate::nft::get_nfts_for_activation; @@ -12,6 +14,8 @@ use instant::Instant; use mm2_err_handle::common_errors::WithInternal; #[cfg(target_arch = "wasm32")] use mm2_metamask::{from_metamask_error, MetamaskError, MetamaskRpcError, WithMetamaskRpcError}; +use mm2_p2p::p2p_ctx::P2PContext; +use proxy_signature::RawMessage; use rpc_task::RpcTaskError; use std::sync::atomic::Ordering; use url::Url; @@ -59,6 +63,8 @@ pub enum EthActivationV2Error { HwError(HwRpcError), #[display(fmt = "Hardware wallet must be called within rpc task framework")] InvalidHardwareWalletCall, + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(CustomTokenError), } impl From for EthActivationV2Error { @@ -89,6 +95,8 @@ impl From for EthActivationV2Error { EthTokenActivationError::UnexpectedDerivationMethod(err) => { EthActivationV2Error::UnexpectedDerivationMethod(err) }, + EthTokenActivationError::PrivKeyPolicyNotAllowed(e) => EthActivationV2Error::PrivKeyPolicyNotAllowed(e), + EthTokenActivationError::CustomTokenError(e) => EthActivationV2Error::CustomTokenError(e), } } } @@ -146,33 +154,27 @@ impl From for EthActivationV2Error { } /// An alternative to `crate::PrivKeyActivationPolicy`, typical only for ETH coin. -#[derive(Clone, Deserialize)] +#[derive(Clone, Deserialize, Default)] pub enum EthPrivKeyActivationPolicy { + #[default] ContextPrivKey, Trezor, #[cfg(target_arch = "wasm32")] Metamask, } -impl Default for EthPrivKeyActivationPolicy { - fn default() -> Self { EthPrivKeyActivationPolicy::ContextPrivKey } -} - impl EthPrivKeyActivationPolicy { pub fn is_hw_policy(&self) -> bool { matches!(self, EthPrivKeyActivationPolicy::Trezor) } } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, Default)] pub enum EthRpcMode { + #[default] Default, #[cfg(target_arch = "wasm32")] Metamask, } -impl Default for EthRpcMode { - fn default() -> Self { EthRpcMode::Default } -} - #[derive(Clone, Deserialize)] pub struct EthActivationV2Request { #[serde(default)] @@ -180,6 +182,8 @@ pub struct EthActivationV2Request { #[serde(default)] pub rpc_mode: EthRpcMode, pub swap_contract_address: Address, + #[serde(default)] + pub swap_v2_contracts: Option, pub fallback_swap_contract: Option
, #[serde(default)] pub contract_supports_watchers: bool, @@ -198,7 +202,7 @@ pub struct EthActivationV2Request { pub struct EthNode { pub url: String, #[serde(default)] - pub gui_auth: bool, + pub komodo_proxy: bool, } #[derive(Display, Serialize, SerializeErrorType)] @@ -210,6 +214,8 @@ pub enum EthTokenActivationError { InvalidPayload(String), Transport(String), UnexpectedDerivationMethod(UnexpectedDerivationMethod), + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), + CustomTokenError(CustomTokenError), } impl From for EthTokenActivationError { @@ -260,6 +266,36 @@ impl From for EthTokenActivationError { fn from(e: String) -> Self { EthTokenActivationError::InternalError(e) } } +impl From for EthTokenActivationError { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { EthTokenActivationError::PrivKeyPolicyNotAllowed(e) } +} + +impl From for EthTokenActivationError { + fn from(e: GenerateSignedMessageError) -> Self { + match e { + GenerateSignedMessageError::InternalError(e) => EthTokenActivationError::InternalError(e), + GenerateSignedMessageError::PrivKeyPolicyNotAllowed(e) => { + EthTokenActivationError::PrivKeyPolicyNotAllowed(e) + }, + } + } +} + +#[derive(Display, Serialize)] +pub enum GenerateSignedMessageError { + #[display(fmt = "Internal: {}", _0)] + InternalError(String), + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), +} + +impl From for GenerateSignedMessageError { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { GenerateSignedMessageError::PrivKeyPolicyNotAllowed(e) } +} + +impl From for GenerateSignedMessageError { + fn from(e: SignatureError) -> Self { GenerateSignedMessageError::InternalError(e.to_string()) } +} + /// Represents the parameters required for activating either an ERC-20 token or an NFT on the Ethereum platform. #[derive(Clone, Deserialize)] #[serde(untagged)] @@ -306,7 +342,11 @@ pub struct NftActivationRequest { #[derive(Clone, Deserialize)] #[serde(tag = "type", content = "info")] pub enum NftProviderEnum { - Moralis { url: Url }, + Moralis { + url: Url, + #[serde(default)] + komodo_proxy: bool, + }, } /// Represents the protocol type for an Ethereum-based token, distinguishing between ERC-20 tokens and NFTs. @@ -341,9 +381,11 @@ pub struct NftProtocol { impl EthCoin { pub async fn initialize_erc20_token( &self, + ticker: String, activation_params: Erc20TokenActivationRequest, + token_conf: Json, protocol: Erc20Protocol, - ticker: String, + is_custom: bool, ) -> MmResult { // TODO // Check if ctx is required. @@ -352,9 +394,24 @@ impl EthCoin { .ok_or_else(|| String::from("No context")) .map_err(EthTokenActivationError::InternalError)?; - let conf = coin_conf(&ctx, &ticker); + // Todo: when custom token config storage is added, this might not be needed + // `is_custom` was added to avoid this unnecessary check for non-custom tokens + if is_custom { + match get_enabled_erc20_by_contract(&ctx, protocol.token_addr).await { + Ok(Some(token)) => { + return MmError::err(EthTokenActivationError::CustomTokenError( + CustomTokenError::TokenWithSameContractAlreadyActivated { + ticker: token.ticker().to_string(), + contract_address: display_eth_address(&protocol.token_addr), + }, + )); + }, + Ok(None) => {}, + Err(e) => return MmError::err(EthTokenActivationError::InternalError(e.to_string())), + } + } - let decimals = match conf["decimals"].as_u64() { + let decimals = match token_conf["decimals"].as_u64() { None | Some(0) => get_token_decimals( &self .web3() @@ -367,27 +424,13 @@ impl EthCoin { Some(d) => d as u8, }; - let web3_instances: Vec = self - .web3_instances - .lock() - .await - .iter() - .map(|node| { - let mut transport = node.web3.transport().clone(); - if let Some(auth) = transport.gui_auth_validation_generator_as_mut() { - auth.coin_ticker = ticker.clone(); - } - let web3 = Web3::new(transport); - Web3Instance { - web3, - is_parity: node.is_parity, - } - }) - .collect(); - let required_confirmations = activation_params .required_confirmations - .unwrap_or_else(|| conf["required_confirmations"].as_u64().unwrap_or(1)) + .unwrap_or_else(|| { + token_conf["required_confirmations"] + .as_u64() + .unwrap_or(self.required_confirmations()) + }) .into(); // Create an abortable system linked to the `MmCtx` so if the app is stopped on `MmArc::stop`, @@ -398,8 +441,12 @@ impl EthCoin { platform: protocol.platform, token_addr: protocol.token_addr, }; - let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; - let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?; + let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &token_conf, &coin_type).await?; + let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &token_conf, &coin_type).await?; + let gas_limit: EthGasLimit = extract_gas_limit_from_conf(&token_conf) + .map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?; + let gas_limit_v2: EthGasLimitV2 = extract_gas_limit_from_conf(&token_conf) + .map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?; let token = EthCoinImpl { priv_key_policy: self.priv_key_policy.clone(), @@ -410,11 +457,12 @@ impl EthCoin { coin_type, sign_message_prefix: self.sign_message_prefix.clone(), swap_contract_address: self.swap_contract_address, + swap_v2_contracts: self.swap_v2_contracts, fallback_swap_contract: self.fallback_swap_contract, contract_supports_watchers: self.contract_supports_watchers, decimals, ticker, - web3_instances: AsyncMutex::new(web3_instances), + web3_instances: AsyncMutex::new(self.web3_instances.lock().await.clone()), history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()), swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), max_eth_tx_type, @@ -427,6 +475,8 @@ impl EthCoin { erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), platform_fee_estimator_state, + gas_limit, + gas_limit_v2, abortable_system, }; @@ -441,28 +491,54 @@ impl EthCoin { /// It fetches NFT details from a given URL to populate the `nfts_infos` field, which stores information about the user's NFTs. /// /// This setup allows the Global NFT to function like a coin, supporting swap operations and providing easy access to NFT details via `nfts_infos`. - pub async fn global_nft_from_platform_coin(&self, url: &Url) -> MmResult { + pub async fn initialize_global_nft( + &self, + original_url: &Url, + komodo_proxy: bool, + ) -> MmResult { let chain = Chain::from_ticker(self.ticker())?; let ticker = chain.to_nft_ticker().to_string(); let ctx = MmArc::from_weak(&self.ctx) .ok_or_else(|| String::from("No context")) .map_err(EthTokenActivationError::InternalError)?; + let p2p_ctx = P2PContext::fetch_from_mm_arc(&ctx); let conf = coin_conf(&ctx, &ticker); + let required_confirmations = AtomicU64::new( + conf["required_confirmations"] + .as_u64() + .unwrap_or_else(|| self.required_confirmations.load(Ordering::Relaxed)), + ); + // Create an abortable system linked to the `platform_coin` (which is self) so if the platform coin is disabled, // all spawned futures related to global Non-Fungible Token will be aborted as well. let abortable_system = self.abortable_system.create_subsystem()?; // Todo: support HD wallet for NFTs, currently we get nfts for enabled address only and there might be some issues when activating NFTs while ETH is activated with HD wallet let my_address = self.derivation_method.single_addr_or_err().await?; - let nft_infos = get_nfts_for_activation(&chain, &my_address, url).await?; + + let proxy_sign = if komodo_proxy { + let uri = Uri::from_str(original_url.as_ref()) + .map_err(|e| EthTokenActivationError::InternalError(e.to_string()))?; + let proxy_sign = RawMessage::sign(p2p_ctx.keypair(), &uri, 0, common::PROXY_REQUEST_EXPIRATION_SEC) + .map_err(|e| EthTokenActivationError::InternalError(e.to_string()))?; + Some(proxy_sign) + } else { + None + }; + + let nft_infos = get_nfts_for_activation(&chain, &my_address, original_url, proxy_sign).await?; let coin_type = EthCoinType::Nft { platform: self.ticker.clone(), }; let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?; + let gas_limit: EthGasLimit = extract_gas_limit_from_conf(&conf) + .map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?; + let gas_limit_v2: EthGasLimitV2 = extract_gas_limit_from_conf(&conf) + .map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?; let global_nft = EthCoinImpl { ticker, @@ -471,14 +547,15 @@ impl EthCoin { derivation_method: self.derivation_method.clone(), sign_message_prefix: self.sign_message_prefix.clone(), swap_contract_address: self.swap_contract_address, + swap_v2_contracts: self.swap_v2_contracts, fallback_swap_contract: self.fallback_swap_contract, contract_supports_watchers: self.contract_supports_watchers, - web3_instances: self.web3_instances.lock().await.clone().into(), + web3_instances: AsyncMutex::new(self.web3_instances.lock().await.clone()), decimals: self.decimals, history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()), swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), max_eth_tx_type, - required_confirmations: AtomicU64::new(self.required_confirmations.load(Ordering::Relaxed)), + required_confirmations, ctx: self.ctx.clone(), chain_id: self.chain_id, trezor_coin: self.trezor_coin.clone(), @@ -487,6 +564,8 @@ impl EthCoin { erc20_tokens_infos: Default::default(), nfts_infos: Arc::new(AsyncMutex::new(nft_infos)), platform_fee_estimator_state, + gas_limit, + gas_limit_v2, abortable_system, }; Ok(EthCoin(Arc::new(global_nft))) @@ -494,7 +573,7 @@ impl EthCoin { } /// Activate eth coin from coin config and private key build policy, -/// version 2 of the activation function, with no intrinsic tokens creation +/// version 2 of the activation function, with no intrinsic tokens creation. pub async fn eth_coin_from_conf_and_request_v2( ctx: &MmArc, ticker: &str, @@ -509,6 +588,23 @@ pub async fn eth_coin_from_conf_and_request_v2( .into()); } + if ctx.use_trading_proto_v2() { + let contracts = req.swap_v2_contracts.as_ref().ok_or_else(|| { + EthActivationV2Error::InvalidPayload( + "swap_v2_contracts must be provided when using trading protocol v2".to_string(), + ) + })?; + if contracts.maker_swap_v2_contract == Address::default() + || contracts.taker_swap_v2_contract == Address::default() + || contracts.nft_maker_swap_v2_contract == Address::default() + { + return Err(EthActivationV2Error::InvalidSwapContractAddr( + "All swap_v2_contracts addresses must be non-zero".to_string(), + ) + .into()); + } + } + if let Some(fallback) = req.fallback_swap_contract { if fallback == Address::default() { return Err(EthActivationV2Error::InvalidFallbackSwapContract( @@ -530,33 +626,9 @@ pub async fn eth_coin_from_conf_and_request_v2( let chain_id = conf["chain_id"].as_u64().ok_or(EthActivationV2Error::ChainIdNotSet)?; let web3_instances = match (req.rpc_mode, &priv_key_policy) { - ( - EthRpcMode::Default, - EthPrivKeyPolicy::Iguana(key_pair) - | EthPrivKeyPolicy::HDWallet { - activated_key: key_pair, - .. - }, - ) => { - let auth_address = key_pair.address(); - let auth_address_str = display_eth_address(&auth_address); - build_web3_instances(ctx, ticker.to_string(), auth_address_str, key_pair, req.nodes.clone()).await? - }, - (EthRpcMode::Default, EthPrivKeyPolicy::Trezor) => { - let crypto_ctx = CryptoCtx::from_ctx(ctx)?; - let secp256k1_key_pair = crypto_ctx.mm2_internal_key_pair(); - let auth_key_pair = KeyPair::from_secret_slice(secp256k1_key_pair.private_ref()) - .map_to_mm(|_| EthActivationV2Error::InternalError("could not get internal keypair".to_string()))?; - let auth_address = auth_key_pair.address(); - let auth_address_str = display_eth_address(&auth_address); - build_web3_instances( - ctx, - ticker.to_string(), - auth_address_str, - &auth_key_pair, - req.nodes.clone(), - ) - .await? + (EthRpcMode::Default, EthPrivKeyPolicy::Iguana(_) | EthPrivKeyPolicy::HDWallet { .. }) + | (EthRpcMode::Default, EthPrivKeyPolicy::Trezor) => { + build_web3_instances(ctx, ticker.to_string(), req.nodes.clone()).await? }, #[cfg(target_arch = "wasm32")] (EthRpcMode::Metamask, EthPrivKeyPolicy::Metamask(_)) => { @@ -599,6 +671,10 @@ pub async fn eth_coin_from_conf_and_request_v2( let coin_type = EthCoinType::Eth; let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(ctx, conf, &coin_type).await?; let max_eth_tx_type = get_max_eth_tx_type_conf(ctx, conf, &coin_type).await?; + let gas_limit: EthGasLimit = extract_gas_limit_from_conf(conf) + .map_to_mm(|e| EthActivationV2Error::InternalError(format!("invalid gas_limit config {}", e)))?; + let gas_limit_v2: EthGasLimitV2 = extract_gas_limit_from_conf(conf) + .map_to_mm(|e| EthActivationV2Error::InternalError(format!("invalid gas_limit config {}", e)))?; let coin = EthCoinImpl { priv_key_policy, @@ -606,6 +682,7 @@ pub async fn eth_coin_from_conf_and_request_v2( coin_type, sign_message_prefix, swap_contract_address: req.swap_contract_address, + swap_v2_contracts: req.swap_v2_contracts, fallback_swap_contract: req.fallback_swap_contract, contract_supports_watchers: req.contract_supports_watchers, decimals: ETH_DECIMALS, @@ -623,6 +700,8 @@ pub async fn eth_coin_from_conf_and_request_v2( erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), platform_fee_estimator_state, + gas_limit, + gas_limit_v2, abortable_system, }; @@ -740,8 +819,6 @@ pub(crate) async fn build_address_and_priv_key_policy( async fn build_web3_instances( ctx: &MmArc, coin_ticker: String, - address: String, - key_pair: &KeyPair, mut eth_nodes: Vec, ) -> MmResult, EthActivationV2Error> { if eth_nodes.is_empty() { @@ -761,57 +838,7 @@ async fn build_web3_instances( .parse() .map_err(|_| EthActivationV2Error::InvalidPayload(format!("{} could not be parsed.", eth_node.url)))?; - let transport = match uri.scheme_str() { - Some("ws") | Some("wss") => { - const TMP_SOCKET_CONNECTION: Duration = Duration::from_secs(20); - - let node = WebsocketTransportNode { - uri: uri.clone(), - gui_auth: eth_node.gui_auth, - }; - - let mut websocket_transport = WebsocketTransport::with_event_handlers(node, event_handlers.clone()); - - if eth_node.gui_auth { - websocket_transport.gui_auth_validation_generator = Some(GuiAuthValidationGenerator { - coin_ticker: coin_ticker.clone(), - secret: key_pair.secret().clone(), - address: address.clone(), - }); - } - - // Temporarily start the connection loop (we close the connection once we have the client version below). - // Ideally, it would be much better to not do this workaround, which requires a lot of refactoring or - // dropping websocket support on parity nodes. - let fut = websocket_transport - .clone() - .start_connection_loop(Some(Instant::now() + TMP_SOCKET_CONNECTION)); - let settings = AbortSettings::info_on_abort(format!("connection loop stopped for {:?}", uri)); - ctx.spawner().spawn_with_settings(fut, settings); - - Web3Transport::Websocket(websocket_transport) - }, - Some("http") | Some("https") => { - let node = HttpTransportNode { - uri, - gui_auth: eth_node.gui_auth, - }; - - build_http_transport( - coin_ticker.clone(), - address.clone(), - key_pair, - node, - event_handlers.clone(), - ) - }, - _ => { - return MmError::err(EthActivationV2Error::InvalidPayload(format!( - "Invalid node address '{uri}'. Only http(s) and ws(s) nodes are supported" - ))); - }, - }; - + let transport = create_transport(ctx, &uri, ð_node, &event_handlers)?; let web3 = Web3::new(transport); let version = match web3.web3().client_version().await { Ok(v) => v, @@ -836,25 +863,67 @@ async fn build_web3_instances( Ok(web3_instances) } -fn build_http_transport( - coin_ticker: String, - address: String, - key_pair: &KeyPair, - node: HttpTransportNode, - event_handlers: Vec, +fn create_transport( + ctx: &MmArc, + uri: &Uri, + eth_node: &EthNode, + event_handlers: &[RpcTransportEventHandlerShared], +) -> MmResult { + match uri.scheme_str() { + Some("ws") | Some("wss") => Ok(create_websocket_transport(ctx, uri, eth_node, event_handlers)), + Some("http") | Some("https") => Ok(create_http_transport(ctx, uri, eth_node, event_handlers)), + _ => MmError::err(EthActivationV2Error::InvalidPayload(format!( + "Invalid node address '{uri}'. Only http(s) and ws(s) nodes are supported" + ))), + } +} + +fn create_websocket_transport( + ctx: &MmArc, + uri: &Uri, + eth_node: &EthNode, + event_handlers: &[RpcTransportEventHandlerShared], ) -> Web3Transport { - use crate::eth::web3_transport::http_transport::HttpTransport; + const TMP_SOCKET_CONNECTION: Duration = Duration::from_secs(20); - let gui_auth = node.gui_auth; - let mut http_transport = HttpTransport::with_event_handlers(node, event_handlers); + let node = WebsocketTransportNode { uri: uri.clone() }; - if gui_auth { - http_transport.gui_auth_validation_generator = Some(GuiAuthValidationGenerator { - coin_ticker, - secret: key_pair.secret().clone(), - address, - }); + let mut websocket_transport = WebsocketTransport::with_event_handlers(node, event_handlers.to_owned()); + + if eth_node.komodo_proxy { + websocket_transport.proxy_sign_keypair = Some(P2PContext::fetch_from_mm_arc(ctx).keypair().clone()); + } + + // Temporarily start the connection loop (we close the connection once we have the client version below). + // Ideally, it would be much better to not do this workaround, which requires a lot of refactoring or + // dropping websocket support on parity nodes. + let fut = websocket_transport + .clone() + .start_connection_loop(Some(Instant::now() + TMP_SOCKET_CONNECTION)); + let settings = AbortSettings::info_on_abort(format!("connection loop stopped for {:?}", uri)); + ctx.spawner().spawn_with_settings(fut, settings); + + Web3Transport::Websocket(websocket_transport) +} + +fn create_http_transport( + ctx: &MmArc, + uri: &Uri, + eth_node: &EthNode, + event_handlers: &[RpcTransportEventHandlerShared], +) -> Web3Transport { + let node = HttpTransportNode { + uri: uri.clone(), + komodo_proxy: eth_node.komodo_proxy, + }; + + let komodo_proxy = node.komodo_proxy; + let mut http_transport = HttpTransport::with_event_handlers(node, event_handlers.to_owned()); + + if komodo_proxy { + http_transport.proxy_sign_keypair = Some(P2PContext::fetch_from_mm_arc(ctx).keypair().clone()); } + Web3Transport::from(http_transport) } diff --git a/mm2src/coins/eth/web3_transport/http_transport.rs b/mm2src/coins/eth/web3_transport/http_transport.rs index e722b16824..378e384df2 100644 --- a/mm2src/coins/eth/web3_transport/http_transport.rs +++ b/mm2src/coins/eth/web3_transport/http_transport.rs @@ -1,9 +1,10 @@ -use crate::eth::web3_transport::handle_gui_auth_payload; use crate::eth::{web3_transport::Web3SendOut, RpcTransportEventHandler, RpcTransportEventHandlerShared, Web3RpcError}; use common::APPLICATION_JSON; +use common::X_AUTH_PAYLOAD; use http::header::CONTENT_TYPE; use jsonrpc_core::{Call, Response}; -use mm2_net::transport::{GuiAuthValidation, GuiAuthValidationGenerator}; +use mm2_p2p::Keypair; +use proxy_signature::RawMessage; use serde_json::Value as Json; use std::ops::Deref; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; @@ -12,13 +13,6 @@ use web3::error::{Error, TransportError}; use web3::helpers::{build_request, to_result_from_output, to_string}; use web3::{RequestId, Transport}; -#[derive(Clone, Serialize)] -pub struct AuthPayload<'a> { - #[serde(flatten)] - pub request: &'a Call, - pub signed_message: GuiAuthValidation, -} - /// Deserialize bytes RPC response into `Result`. /// Implementation copied from Web3 HTTP transport pub(crate) fn de_rpc_response(response: T, rpc_url: &str) -> Result @@ -46,13 +40,13 @@ pub struct HttpTransport { pub(crate) last_request_failed: Arc, node: HttpTransportNode, event_handlers: Vec, - pub(crate) gui_auth_validation_generator: Option, + pub(crate) proxy_sign_keypair: Option, } #[derive(Clone, Debug)] pub struct HttpTransportNode { pub(crate) uri: http::Uri, - pub(crate) gui_auth: bool, + pub(crate) komodo_proxy: bool, } impl HttpTransport { @@ -63,7 +57,7 @@ impl HttpTransport { id: Arc::new(AtomicUsize::new(0)), node, event_handlers: Default::default(), - gui_auth_validation_generator: None, + proxy_sign_keypair: None, last_request_failed: Arc::new(AtomicBool::new(false)), } } @@ -74,7 +68,7 @@ impl HttpTransport { id: Arc::new(AtomicUsize::new(0)), node, event_handlers, - gui_auth_validation_generator: None, + proxy_sign_keypair: None, last_request_failed: Arc::new(AtomicBool::new(false)), } } @@ -108,26 +102,33 @@ async fn send_request(request: Call, transport: HttpTransport) -> Result serialized_request = r, - Err(e) => { - return Err(request_failed_error(request, e)); - }, - }; - } + let serialized_request = to_string(&request); + let request_bytes = serialized_request.as_bytes(); - transport - .event_handlers - .on_outgoing_request(serialized_request.as_bytes()); + transport.event_handlers.on_outgoing_request(request_bytes); - let mut req = http::Request::new(serialized_request.into_bytes()); + let mut req = http::Request::new(request_bytes.to_owned()); *req.method_mut() = http::Method::POST; *req.uri_mut() = transport.node.uri.clone(); req.headers_mut() .insert(CONTENT_TYPE, HeaderValue::from_static(APPLICATION_JSON)); + + if let Some(proxy_sign_keypair) = &transport.proxy_sign_keypair { + let proxy_sign = RawMessage::sign( + proxy_sign_keypair, + &transport.node.uri, + request_bytes.len(), + common::PROXY_REQUEST_EXPIRATION_SEC, + ) + .map_err(|e| request_failed_error(&request, Web3RpcError::Internal(e.to_string())))?; + + let proxy_sign_serialized = serde_json::to_string(&proxy_sign) + .map_err(|e| request_failed_error(&request, Web3RpcError::Internal(e.to_string())))?; + + req.headers_mut() + .insert(X_AUTH_PAYLOAD, proxy_sign_serialized.parse().unwrap()); + } + let timeout = Timer::sleep(REQUEST_TIMEOUT_S); let req = Box::pin(slurp_req(req)); let rc = select(req, timeout).await; @@ -144,14 +145,14 @@ async fn send_request(request: Call, transport: HttpTransport) -> Result r, Err(err) => { - return Err(request_failed_error(request, Web3RpcError::Transport(err.to_string()))); + return Err(request_failed_error(&request, Web3RpcError::Transport(err.to_string()))); }, }; @@ -159,7 +160,7 @@ async fn send_request(request: Call, transport: HttpTransport) -> Result Result r, Err(err) => { return Err(request_failed_error( - request, + &request, Web3RpcError::InvalidResponse(format!("Server: '{}', error: {}", transport.node.uri, err)), )); }, @@ -184,28 +185,41 @@ async fn send_request(request: Call, transport: HttpTransport) -> Result Result { - let mut serialized_request = to_string(&request); - - if transport.node.gui_auth { - match handle_gui_auth_payload(&transport.gui_auth_validation_generator, &request) { - Ok(r) => serialized_request = r, - Err(e) => { - return Err(request_failed_error( - request, - Web3RpcError::Transport(format!("Server: '{}', error: {}", transport.node.uri, e)), - )); - }, - }; - } + let serialized_request = to_string(&request); + let request_bytes = serialized_request.as_bytes(); + + let proxy_sign_header = if let Some(proxy_sign_keypair) = &transport.proxy_sign_keypair { + let proxy_sign = RawMessage::sign( + proxy_sign_keypair, + &transport.node.uri, + request_bytes.len(), + common::PROXY_REQUEST_EXPIRATION_SEC, + ) + .map_err(|e| request_failed_error(&request, Web3RpcError::Internal(e.to_string())))?; + + let proxy_sign_serialized = serde_json::to_string(&proxy_sign) + .map_err(|e| request_failed_error(&request, Web3RpcError::Internal(e.to_string())))?; + + Some(proxy_sign_serialized) + } else { + None + }; - match send_request_once(serialized_request, &transport.node.uri, &transport.event_handlers).await { + match send_request_once( + serialized_request, + &transport.node.uri, + &transport.event_handlers, + proxy_sign_header, + ) + .await + { Ok(response_json) => Ok(response_json), Err(Error::Transport(e)) => Err(request_failed_error( - request, + &request, Web3RpcError::Transport(format!("Server: '{}', error: {}", transport.node.uri, e)), )), Err(e) => Err(request_failed_error( - request, + &request, Web3RpcError::InvalidResponse(format!("Server: '{}', error: {}", transport.node.uri, e)), )), } @@ -216,6 +230,7 @@ async fn send_request_once( request_payload: String, uri: &http::Uri, event_handlers: &Vec, + proxy_sign_header: Option, ) -> Result { use http::header::ACCEPT; use mm2_net::wasm::http::FetchRequest; @@ -223,11 +238,19 @@ async fn send_request_once( // account for outgoing traffic event_handlers.on_outgoing_request(request_payload.as_bytes()); - let (status_code, response_str) = FetchRequest::post(&uri.to_string()) + let mut request = FetchRequest::post(&uri.to_string()); + + request = request .cors() .body_utf8(request_payload) .header(ACCEPT.as_str(), APPLICATION_JSON) - .header(CONTENT_TYPE.as_str(), APPLICATION_JSON) + .header(CONTENT_TYPE.as_str(), APPLICATION_JSON); + + if let Some(proxy_sign_header) = proxy_sign_header { + request = request.header(X_AUTH_PAYLOAD, &proxy_sign_header); + } + + let (status_code, response_str) = request .request_str() .await .map_err(|e| Error::Transport(TransportError::Message(ERRL!("{:?}", e))))?; @@ -252,7 +275,7 @@ async fn send_request_once( } } -fn request_failed_error(request: Call, error: Web3RpcError) -> Error { +fn request_failed_error(request: &Call, error: Web3RpcError) -> Error { let error = format!("request {:?} failed: {}", request, error); Error::Transport(TransportError::Message(error)) } diff --git a/mm2src/coins/eth/web3_transport/mod.rs b/mm2src/coins/eth/web3_transport/mod.rs index dcbdf6ef90..e064e6025a 100644 --- a/mm2src/coins/eth/web3_transport/mod.rs +++ b/mm2src/coins/eth/web3_transport/mod.rs @@ -2,15 +2,11 @@ use ethereum_types::U256; use futures::future::BoxFuture; use jsonrpc_core::Call; #[cfg(target_arch = "wasm32")] use mm2_metamask::MetamaskResult; -use mm2_net::transport::GuiAuthValidationGenerator; use serde_json::Value as Json; use serde_json::Value; use std::sync::atomic::Ordering; -use web3::helpers::to_string; use web3::{Error, RequestId, Transport}; -use self::http_transport::AuthPayload; -use super::{EthCoin, GuiAuthMessages, Web3RpcError}; use crate::RpcTransportEventHandlerShared; pub(crate) mod http_transport; @@ -66,15 +62,6 @@ impl Web3Transport { pub fn new_http(node: http_transport::HttpTransportNode) -> Web3Transport { http_transport::HttpTransport::new(node).into() } - - pub fn gui_auth_validation_generator_as_mut(&mut self) -> Option<&mut GuiAuthValidationGenerator> { - match self { - Web3Transport::Http(http) => http.gui_auth_validation_generator.as_mut(), - Web3Transport::Websocket(websocket) => websocket.gui_auth_validation_generator.as_mut(), - #[cfg(target_arch = "wasm32")] - Web3Transport::Metamask(_) => None, - } - } } impl Transport for Web3Transport { @@ -133,35 +120,3 @@ pub struct FeeHistoryResult { #[serde(rename = "reward")] pub priority_rewards: Option>>, } - -/// Generates a signed message and inserts it into the request payload. -pub(super) fn handle_gui_auth_payload( - gui_auth_validation_generator: &Option, - request: &Call, -) -> Result { - let generator = match gui_auth_validation_generator.clone() { - Some(gen) => gen, - None => { - return Err(Web3RpcError::Internal( - "GuiAuthValidationGenerator is not provided for".to_string(), - )); - }, - }; - - let signed_message = match EthCoin::generate_gui_auth_signed_validation(generator) { - Ok(t) => t, - Err(e) => { - return Err(Web3RpcError::Internal(format!( - "GuiAuth signed message generation failed. Error: {:?}", - e - ))); - }, - }; - - let auth_request = AuthPayload { - request, - signed_message, - }; - - Ok(to_string(&auth_request)) -} diff --git a/mm2src/coins/eth/web3_transport/websocket_transport.rs b/mm2src/coins/eth/web3_transport/websocket_transport.rs index 8b130c14fe..263553c248 100644 --- a/mm2src/coins/eth/web3_transport/websocket_transport.rs +++ b/mm2src/coins/eth/web3_transport/websocket_transport.rs @@ -5,7 +5,6 @@ //! less bandwidth. This efficiency is achieved by avoiding the handling of TCP handshakes (connection reusability) //! for each request. -use super::handle_gui_auth_payload; use super::http_transport::de_rpc_response; use crate::eth::eth_rpc::ETH_RPC_REQUEST_TIMEOUT; use crate::eth::web3_transport::Web3SendOut; @@ -21,7 +20,8 @@ use futures_ticker::Ticker; use futures_util::{FutureExt, SinkExt, StreamExt}; use instant::{Duration, Instant}; use jsonrpc_core::Call; -use mm2_net::transport::GuiAuthValidationGenerator; +use mm2_p2p::Keypair; +use proxy_signature::{ProxySign, RawMessage}; use std::sync::atomic::AtomicBool; use std::sync::{atomic::{AtomicUsize, Ordering}, Arc}; @@ -37,7 +37,6 @@ const KEEPALIVE_DURATION: Duration = Duration::from_secs(10); #[derive(Clone, Debug)] pub(crate) struct WebsocketTransportNode { pub(crate) uri: http::Uri, - pub(crate) gui_auth: bool, } #[derive(Clone, Debug)] @@ -46,7 +45,7 @@ pub struct WebsocketTransport { pub(crate) last_request_failed: Arc, node: WebsocketTransportNode, event_handlers: Vec, - pub(crate) gui_auth_validation_generator: Option, + pub(crate) proxy_sign_keypair: Option, controller_channel: ControllerChannel, connection_guard: Arc>, } @@ -92,17 +91,12 @@ impl WebsocketTransport { rx: Arc::new(AsyncMutex::new(req_rx)), }, connection_guard: Arc::new(AsyncMutex::new(())), - gui_auth_validation_generator: None, + proxy_sign_keypair: None, last_request_failed: Arc::new(AtomicBool::new(false)), } } - async fn handle_keepalive( - &self, - wsocket: &mut WebSocketStream, - response_notifiers: &mut ExpirableMap>>, - expires_at: Option, - ) -> OuterAction { + async fn handle_keepalive(&self, wsocket: &mut WebSocketStream, expires_at: Option) -> OuterAction { const SIMPLE_REQUEST: &str = r#"{"jsonrpc":"2.0","method":"net_version","params":[],"id": 0 }"#; if let Some(expires_at) = expires_at { @@ -112,10 +106,7 @@ impl WebsocketTransport { } } - // Drop expired response notifier channels - response_notifiers.clear_expired_entries(); - - let mut should_continue = Default::default(); + let mut should_continue = false; for _ in 0..MAX_ATTEMPTS { match wsocket .send(tokio_tungstenite_wasm::Message::Text(SIMPLE_REQUEST.to_string())) @@ -206,9 +197,6 @@ impl WebsocketTransport { } if let Some(id) = inc_event.get("id") { - // just to ensure we don't have outdated entries - response_notifiers.clear_expired_entries(); - let request_id = id.as_u64().unwrap_or_default() as usize; if let Some(notifier) = response_notifiers.remove(&request_id) { @@ -279,7 +267,7 @@ impl WebsocketTransport { loop { futures_util::select! { _ = keepalive_interval.next().fuse() => { - match self.handle_keepalive(&mut wsocket, &mut response_notifiers, expires_at).await { + match self.handle_keepalive(&mut wsocket, expires_at).await { OuterAction::None => {}, OuterAction::Continue => continue, OuterAction::Break => break, @@ -339,25 +327,40 @@ async fn send_request( request_id: RequestId, event_handlers: Vec, ) -> Result { - let mut serialized_request = to_string(&request); + /// komodo-defi-proxy expects proxy signatures in the socket messages as they + /// cannot be provided through headers. + #[derive(Serialize)] + struct ProxyWrapper<'a> { + #[serde(flatten)] + pub request: &'a Call, + pub proxy_sign: ProxySign, + } - if transport.node.gui_auth { - match handle_gui_auth_payload(&transport.gui_auth_validation_generator, &request) { - Ok(r) => serialized_request = r, - Err(e) => { - return Err(Error::Transport(TransportError::Message(format!( - "Couldn't generate signed message payload for {:?}. Error: {e}", - request - )))); - }, + let mut serialized_request = to_string(&request); + let request_bytes = serialized_request.as_bytes().to_owned(); + + if let Some(proxy_sign_keypair) = &transport.proxy_sign_keypair { + let proxy_sign = RawMessage::sign( + proxy_sign_keypair, + &transport.node.uri, + request_bytes.len(), + common::PROXY_REQUEST_EXPIRATION_SEC, + ) + .map_err(|e| Error::Transport(TransportError::Message(e.to_string())))?; + + let wrapper = ProxyWrapper { + request: &request, + proxy_sign, }; + + serialized_request = serde_json::to_string(&wrapper)?; } let mut tx = transport.controller_channel.tx.clone(); - let (notification_sender, notification_receiver) = futures::channel::oneshot::channel::>(); + let (notification_sender, notification_receiver) = oneshot::channel::>(); - event_handlers.on_outgoing_request(serialized_request.as_bytes()); + event_handlers.on_outgoing_request(&request_bytes); tx.send(ControllerMessage::Request(WsRequest { request_id, diff --git a/mm2src/coins/hd_wallet/account_ops.rs b/mm2src/coins/hd_wallet/account_ops.rs index f619753b6b..eeba0c15f2 100644 --- a/mm2src/coins/hd_wallet/account_ops.rs +++ b/mm2src/coins/hd_wallet/account_ops.rs @@ -1,5 +1,5 @@ -use super::{HDAddressOps, HDAddressesCache, InvalidBip44ChainError}; -use crypto::{Bip44Chain, DerivationPath, HDPathToAccount, Secp256k1ExtendedPublicKey}; +use super::{ExtendedPublicKeyOps, HDAddressOps, HDAddressesCache, InvalidBip44ChainError}; +use crypto::{Bip44Chain, DerivationPath, HDPathToAccount}; use mm2_err_handle::prelude::*; /// `HDAccountOps` Trait @@ -13,11 +13,14 @@ use mm2_err_handle::prelude::*; /// Implementors of this trait should provide details about such HD account like its specific derivation path, known addresses, and its index. pub trait HDAccountOps { type HDAddress: HDAddressOps + Clone + Send; + /// Any type that represents an extended public key, whether it's secp256k1, ed25519, Schnorr, etc. + /// This type should implement the `ExtendedPublicKeyOps` trait. + type ExtendedPublicKey: ExtendedPublicKeyOps; /// A constructor for any type that implements `HDAccountOps`. fn new( account_id: u32, - account_extended_pubkey: Secp256k1ExtendedPublicKey, + account_extended_pubkey: Self::ExtendedPublicKey, account_derivation_path: HDPathToAccount, ) -> Self; @@ -47,5 +50,5 @@ pub trait HDAccountOps { fn derived_addresses(&self) -> &HDAddressesCache; /// Fetches the extended public key associated with this account. - fn extended_pubkey(&self) -> &Secp256k1ExtendedPublicKey; + fn extended_pubkey(&self) -> &Self::ExtendedPublicKey; } diff --git a/mm2src/coins/hd_wallet/coin_ops.rs b/mm2src/coins/hd_wallet/coin_ops.rs index 4291b1809f..6a70b8c037 100644 --- a/mm2src/coins/hd_wallet/coin_ops.rs +++ b/mm2src/coins/hd_wallet/coin_ops.rs @@ -1,14 +1,16 @@ -use super::{inner_impl, AccountUpdatingError, AddressDerivingError, AddressDerivingResult, HDAccountOps, - HDCoinAddress, HDCoinHDAccount, HDCoinHDAddress, HDConfirmAddress, HDWalletOps, +use super::{inner_impl, AccountUpdatingError, AddressDerivingError, ExtendedPublicKeyOps, HDAccountOps, HDCoinAddress, + HDCoinExtendedPubkey, HDCoinHDAccount, HDCoinHDAddress, HDConfirmAddress, HDWalletOps, NewAddressDeriveConfirmError, NewAddressDerivingError}; use crate::hd_wallet::{HDAddressOps, HDWalletStorageOps, TrezorCoinError}; use async_trait::async_trait; use bip32::{ChildNumber, DerivationPath}; -use crypto::{Bip44Chain, Secp256k1ExtendedPublicKey}; +use crypto::Bip44Chain; use itertools::Itertools; use mm2_err_handle::mm_error::{MmError, MmResult}; use std::collections::HashMap; +type AddressDerivingResult = MmResult; + /// Unique identifier for an HD address within an account. #[derive(Clone, Eq, Hash, PartialEq)] pub struct HDAddressId { @@ -33,7 +35,7 @@ pub trait HDWalletCoinOps { /// Derives an address for the coin that implements this trait from an extended public key and a derivation path. fn address_from_extended_pubkey( &self, - extended_pubkey: &Secp256k1ExtendedPublicKey, + extended_pubkey: &HDCoinExtendedPubkey, derivation_path: DerivationPath, ) -> HDCoinHDAddress; @@ -143,9 +145,7 @@ pub trait HDWalletCoinOps { chain: Bip44Chain, ) -> AddressDerivingResult>> { let known_addresses_number = hd_account.known_addresses_number(chain)?; - let address_ids = (0..known_addresses_number) - .into_iter() - .map(|address_id| HDAddressId { chain, address_id }); + let address_ids = (0..known_addresses_number).map(|address_id| HDAddressId { chain, address_id }); self.derive_addresses(hd_account, address_ids).await } diff --git a/mm2src/coins/hd_wallet/mod.rs b/mm2src/coins/hd_wallet/mod.rs index b8db30733a..1d77db3543 100644 --- a/mm2src/coins/hd_wallet/mod.rs +++ b/mm2src/coins/hd_wallet/mod.rs @@ -33,7 +33,7 @@ pub use errors::{AccountUpdatingError, AddressDerivingError, HDExtractPubkeyErro NewAddressDerivingError, TrezorCoinError}; mod pubkey; -pub use pubkey::{ExtractExtendedPubkey, HDXPubExtractor, RpcTaskXPubExtractor}; +pub use pubkey::{ExtendedPublicKeyOps, ExtractExtendedPubkey, HDXPubExtractor, RpcTaskXPubExtractor}; mod storage; #[cfg(target_arch = "wasm32")] @@ -49,21 +49,19 @@ pub use wallet_ops::HDWalletOps; mod withdraw_ops; pub use withdraw_ops::{HDCoinWithdrawOps, WithdrawFrom, WithdrawSenderAddress}; -pub(crate) type AddressDerivingResult = MmResult; pub(crate) type HDAccountsMap = BTreeMap; pub(crate) type HDAccountsMutex = AsyncMutex>; pub(crate) type HDAccountsMut<'a, HDAccount> = AsyncMutexGuard<'a, HDAccountsMap>; pub(crate) type HDAccountMut<'a, HDAccount> = AsyncMappedMutexGuard<'a, HDAccountsMap, HDAccount>; +type HDWalletHDAddress = <::HDAccount as HDAccountOps>::HDAddress; +type HDCoinHDAddress = HDWalletHDAddress<::HDWallet>; pub(crate) type HDWalletAddress = <<::HDAccount as HDAccountOps>::HDAddress as HDAddressOps>::Address; -pub(crate) type HDWalletPubKey = - <<::HDAccount as HDAccountOps>::HDAddress as HDAddressOps>::Pubkey; pub(crate) type HDCoinAddress = HDWalletAddress<::HDWallet>; -pub(crate) type HDCoinPubKey = HDWalletPubKey<::HDWallet>; -pub(crate) type HDWalletHDAddress = <::HDAccount as HDAccountOps>::HDAddress; -pub(crate) type HDCoinHDAddress = HDWalletHDAddress<::HDWallet>; -pub(crate) type HDWalletHDAccount = ::HDAccount; +type HDWalletExtendedPubkey = <::HDAccount as HDAccountOps>::ExtendedPublicKey; +pub(crate) type HDCoinExtendedPubkey = HDWalletExtendedPubkey<::HDWallet>; pub(crate) type HDCoinHDAccount = HDWalletHDAccount<::HDWallet>; +type HDWalletHDAccount = ::HDAccount; pub(crate) const DEFAULT_GAP_LIMIT: u32 = 20; const DEFAULT_ACCOUNT_LIMIT: u32 = ChildNumber::HARDENED_FLAG; @@ -119,14 +117,15 @@ impl HDAddressesCache { /// A generic HD account that can be used with any HD wallet. #[derive(Clone, Debug)] -pub struct HDAccount +pub struct HDAccount where HDAddress: HDAddressOps + Send, + ExtendedPublicKey: ExtendedPublicKeyOps, { pub account_id: u32, /// [Extended public key](https://learnmeabitcoin.com/technical/extended-keys) that corresponds to the derivation path: /// `m/purpose'/coin_type'/account'`. - pub extended_pubkey: Secp256k1ExtendedPublicKey, + pub extended_pubkey: ExtendedPublicKey, /// [`HDWallet::derivation_path`] derived by [`HDAccount::account_id`]. pub account_derivation_path: HDPathToAccount, /// The number of addresses that we know have been used by the user. @@ -142,15 +141,17 @@ where pub derived_addresses: HDAddressesCache, } -impl HDAccountOps for HDAccount +impl HDAccountOps for HDAccount where HDAddress: HDAddressOps + Clone + Send, + ExtendedPublicKey: ExtendedPublicKeyOps, { type HDAddress = HDAddress; + type ExtendedPublicKey = ExtendedPublicKey; fn new( account_id: u32, - extended_pubkey: Secp256k1ExtendedPublicKey, + extended_pubkey: Self::ExtendedPublicKey, account_derivation_path: HDPathToAccount, ) -> Self { HDAccount { @@ -194,12 +195,14 @@ where fn derived_addresses(&self) -> &HDAddressesCache { &self.derived_addresses } - fn extended_pubkey(&self) -> &Secp256k1ExtendedPublicKey { &self.extended_pubkey } + fn extended_pubkey(&self) -> &Self::ExtendedPublicKey { &self.extended_pubkey } } -impl HDAccountStorageOps for HDAccount +impl HDAccountStorageOps for HDAccount where HDAddress: HDAddressOps + Send, + ExtendedPublicKey: ExtendedPublicKeyOps, + ::Err: Display, { fn try_from_storage_item( wallet_der_path: &HDPathToCoin, @@ -214,7 +217,8 @@ where let account_derivation_path = wallet_der_path .derive(account_child) .map_to_mm(StandardHDPathError::from)?; - let extended_pubkey = Secp256k1ExtendedPublicKey::from_str(&account_info.account_xpub)?; + let extended_pubkey = ExtendedPublicKey::from_str(&account_info.account_xpub) + .map_err(|e| HDWalletStorageError::ErrorDeserializing(e.to_string()))?; let capacity = account_info.external_addresses_number + account_info.internal_addresses_number + DEFAULT_GAP_LIMIT; Ok(HDAccount { @@ -237,15 +241,17 @@ where } } -pub async fn load_hd_accounts_from_storage( +pub async fn load_hd_accounts_from_storage( hd_wallet_storage: &HDWalletCoinStorage, derivation_path: &HDPathToCoin, -) -> HDWalletStorageResult>> +) -> HDWalletStorageResult>> where HDAddress: HDAddressOps + Send, + ExtendedPublicKey: ExtendedPublicKeyOps, + ::Err: Display, { let accounts = hd_wallet_storage.load_all_accounts().await?; - let res: HDWalletStorageResult>> = accounts + let res: HDWalletStorageResult>> = accounts .iter() .map(|account_info| { let account = HDAccount::try_from_storage_item(derivation_path, account_info)?; @@ -380,7 +386,7 @@ pub async fn create_new_account<'a, Coin, XPubExtractor, HDWallet, HDAccount>( account_id: Option, ) -> MmResult>, NewAccountCreationError> where - Coin: ExtractExtendedPubkey + Sync, + Coin: ExtractExtendedPubkey> + Sync, HDWallet: HDWalletOps + HDWalletStorageOps + Sync, XPubExtractor: HDXPubExtractor + Send, HDAccount: 'a + HDAccountOps + HDAccountStorageOps, diff --git a/mm2src/coins/hd_wallet/pubkey.rs b/mm2src/coins/hd_wallet/pubkey.rs index 0c27030af9..7babb12bd5 100644 --- a/mm2src/coins/hd_wallet/pubkey.rs +++ b/mm2src/coins/hd_wallet/pubkey.rs @@ -1,7 +1,10 @@ +#[cfg(feature = "enable-sia")] +use crate::siacoin::sia_hd_wallet::Ed25519ExtendedPublicKey; use crate::CoinProtocol; use super::*; use async_trait::async_trait; +use bip32::Prefix; use crypto::hw_rpc_task::HwConnectStatuses; use crypto::trezor::trezor_rpc_task::{TrezorRpcTaskProcessor, TryIntoUserAction}; use crypto::trezor::utxo::IGNORE_XPUB_MAGIC; @@ -14,6 +17,28 @@ use std::sync::Arc; const SHOW_PUBKEY_ON_DISPLAY: bool = false; +/// A trait that should be implemented by any extended public key type +/// to allow it to work with the HD wallet traits. +pub trait ExtendedPublicKeyOps: FromStr + Sized { + /// Derives a child extended public key from the current one. + fn derive_child(&self, child_number: ChildNumber) -> Result; + /// Converts the extended public key to a string. + fn to_string(&self, prefix: Prefix) -> String; +} + +impl ExtendedPublicKeyOps for Secp256k1ExtendedPublicKey { + fn derive_child(&self, child_number: ChildNumber) -> Result { self.derive_child(child_number) } + + fn to_string(&self, prefix: Prefix) -> String { self.to_string(prefix) } +} + +#[cfg(feature = "enable-sia")] +impl ExtendedPublicKeyOps for Ed25519ExtendedPublicKey { + fn derive_child(&self, child_number: ChildNumber) -> Result { self.derive_child(child_number) } + + fn to_string(&self, prefix: Prefix) -> String { self.to_string(prefix) } +} + /// This trait should be implemented for coins /// to support extracting extended public keys from any depth. /// The extraction can be from either an internal or external wallet. diff --git a/mm2src/coins/hd_wallet/storage/mod.rs b/mm2src/coins/hd_wallet/storage/mod.rs index ced30d5bb8..cb8f11b01d 100644 --- a/mm2src/coins/hd_wallet/storage/mod.rs +++ b/mm2src/coins/hd_wallet/storage/mod.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use crypto::{CryptoCtx, CryptoCtxError, HDPathToCoin, XPub}; +use crypto::{Bip32Error, CryptoCtx, CryptoCtxError, HDPathToCoin, StandardHDPathError, XPub}; use derive_more::Display; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -47,10 +47,18 @@ pub enum HDWalletStorageError { Internal(String), } +impl From for HDWalletStorageError { + fn from(e: Bip32Error) -> Self { HDWalletStorageError::ErrorDeserializing(e.to_string()) } +} + impl From for HDWalletStorageError { fn from(e: CryptoCtxError) -> Self { HDWalletStorageError::Internal(e.to_string()) } } +impl From for HDWalletStorageError { + fn from(e: StandardHDPathError) -> Self { HDWalletStorageError::ErrorDeserializing(e.to_string()) } +} + impl HDWalletStorageError { pub fn is_deserializing_err(&self) -> bool { matches!(self, HDWalletStorageError::ErrorDeserializing(_)) } } diff --git a/mm2src/coins/hd_wallet/storage/sqlite_storage.rs b/mm2src/coins/hd_wallet/storage/sqlite_storage.rs index 898f4c8823..f430eac042 100644 --- a/mm2src/coins/hd_wallet/storage/sqlite_storage.rs +++ b/mm2src/coins/hd_wallet/storage/sqlite_storage.rs @@ -101,7 +101,7 @@ impl HDWalletStorageInternalOps for HDWalletSqliteStorage { where Self: Sized, { - let shared = ctx.shared_sqlite_conn.as_option().or_mm_err(|| { + let shared = ctx.shared_sqlite_conn.get().or_mm_err(|| { HDWalletStorageError::Internal("'MmCtx::shared_sqlite_conn' is not initialized".to_owned()) })?; let storage = HDWalletSqliteStorage { diff --git a/mm2src/coins/hd_wallet/withdraw_ops.rs b/mm2src/coins/hd_wallet/withdraw_ops.rs index 7eeafbcd12..7f1aa8b19c 100644 --- a/mm2src/coins/hd_wallet/withdraw_ops.rs +++ b/mm2src/coins/hd_wallet/withdraw_ops.rs @@ -1,11 +1,14 @@ use super::{HDPathAccountToAddressId, HDWalletOps, HDWithdrawError}; -use crate::hd_wallet::{HDAccountOps, HDAddressOps, HDCoinAddress, HDCoinPubKey, HDWalletCoinOps}; +use crate::hd_wallet::{HDAccountOps, HDAddressOps, HDCoinAddress, HDWalletCoinOps}; use async_trait::async_trait; use bip32::DerivationPath; use crypto::{StandardHDPath, StandardHDPathError}; use mm2_err_handle::prelude::*; use std::str::FromStr; +type HDCoinPubKey = + <<<::HDWallet as HDWalletOps>::HDAccount as HDAccountOps>::HDAddress as HDAddressOps>::Pubkey; + /// Represents the source of the funds for a withdrawal operation. #[derive(Clone, Deserialize, Serialize)] #[serde(untagged)] diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 2feb7ef014..67b27ba8f4 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -610,43 +610,40 @@ impl LightningCoin { #[async_trait] impl SwapOps for LightningCoin { // Todo: This uses dummy data for now for the sake of swap P.O.C., this should be implemented probably after agreeing on how fees will work for lightning - fn send_taker_fee(&self, _fee_addr: &[u8], _dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { - let fut = async move { Ok(TransactionEnum::LightningPayment(PaymentHash([1; 32]))) }; - Box::new(fut.boxed().compat()) + async fn send_taker_fee( + &self, + _fee_addr: &[u8], + _dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { + Ok(TransactionEnum::LightningPayment(PaymentHash([1; 32]))) } - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { let invoice = match maker_payment_args.payment_instructions.clone() { Some(PaymentInstructions::Lightning(invoice)) => invoice, - _ => try_tx_fus!(ERR!("Invalid instructions, ligntning invoice is expected")), + _ => try_tx_s!(ERR!("Invalid instructions, ligntning invoice is expected")), }; - let coin = self.clone(); - let fut = async move { - // No need for max_total_cltv_expiry_delta for lightning maker payment since the maker is the side that reveals the secret/preimage - let payment = try_tx_s!(coin.pay_invoice(invoice, None).await); - Ok(payment.payment_hash.into()) - }; - Box::new(fut.boxed().compat()) + // No need for max_total_cltv_expiry_delta for lightning maker payment since the maker is the side that reveals the secret/preimage + let payment = try_tx_s!(self.pay_invoice(invoice, None).await); + Ok(payment.payment_hash.into()) } - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { let invoice = match taker_payment_args.payment_instructions.clone() { Some(PaymentInstructions::Lightning(invoice)) => invoice, - _ => try_tx_fus!(ERR!("Invalid instructions, ligntning invoice is expected")), + _ => try_tx_s!(ERR!("Invalid instructions, ligntning invoice is expected")), }; let max_total_cltv_expiry_delta = self .estimate_blocks_from_duration(taker_payment_args.time_lock_duration) .try_into() .expect("max_total_cltv_expiry_delta shouldn't exceed u32::MAX"); - let coin = self.clone(); - let fut = async move { - // Todo: The path/s used is already logged when PaymentPathSuccessful/PaymentPathFailed events are fired, it might be better to save it to the DB and retrieve it with the payment info. - let payment = try_tx_s!(coin.pay_invoice(invoice, Some(max_total_cltv_expiry_delta)).await); - Ok(payment.payment_hash.into()) - }; - Box::new(fut.boxed().compat()) + // Todo: The path/s used is already logged when PaymentPathSuccessful/PaymentPathFailed events are fired, it might be better to save it to the DB and retrieve it with the payment info. + let payment = try_tx_s!(self.pay_invoice(invoice, Some(max_total_cltv_expiry_delta)).await); + Ok(payment.payment_hash.into()) } #[inline] @@ -684,9 +681,7 @@ impl SwapOps for LightningCoin { } // Todo: This validates the dummy fee for now for the sake of swap P.O.C., this should be implemented probably after agreeing on how fees will work for lightning - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { - Box::new(futures01::future::ok(())) - } + async fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { Ok(()) } #[inline] async fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { @@ -698,29 +693,26 @@ impl SwapOps for LightningCoin { self.validate_swap_payment(input).compat().await } - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, - ) -> Box, Error = String> + Send> { + ) -> Result, String> { let invoice = match if_my_payment_sent_args.payment_instructions.clone() { Some(PaymentInstructions::Lightning(invoice)) => invoice, - _ => try_f!(ERR!("Invalid instructions, ligntning invoice is expected")), + _ => return ERR!("Invalid instructions, ligntning invoice is expected"), }; let payment_hash = PaymentHash((invoice.payment_hash()).into_inner()); let payment_hex = hex::encode(payment_hash.0); - let coin = self.clone(); - let fut = async move { - match coin.db.get_payment_from_db(payment_hash).await { - Ok(maybe_payment) => Ok(maybe_payment.map(|p| p.payment_hash.into())), - Err(e) => ERR!( - "Unable to check if payment {} is in db or not error: {}", - payment_hex, - e - ), - } - }; - Box::new(fut.boxed().compat()) + + match self.db.get_payment_from_db(payment_hash).await { + Ok(maybe_payment) => Ok(maybe_payment.map(|p| p.payment_hash.into())), + Err(e) => ERR!( + "Unable to check if payment {} is in db or not error: {}", + payment_hex, + e + ), + } } // Todo: need to also check on-chain spending @@ -1172,58 +1164,53 @@ impl MarketCoinOps for LightningCoin { Box::new(fut.boxed().compat()) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { - let payment_hash = try_tx_fus!(payment_hash_from_slice(args.tx_bytes)); + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { + let payment_hash = try_tx_s!(payment_hash_from_slice(args.tx_bytes)); let payment_hex = hex::encode(payment_hash.0); - let coin = self.clone(); - let wait_until = args.wait_until; - let fut = async move { - loop { - if now_sec() > wait_until { - return Err(TransactionErr::Plain(ERRL!( - "Waited too long until {} for payment {} to be spent", - wait_until, - payment_hex - ))); - } + loop { + if now_sec() > args.wait_until { + return Err(TransactionErr::Plain(ERRL!( + "Waited too long until {} for payment {} to be spent", + args.wait_until, + payment_hex + ))); + } - match coin.db.get_payment_from_db(payment_hash).await { - Ok(Some(payment)) => match payment.status { - HTLCStatus::Pending => (), - HTLCStatus::Claimable => { - return Err(TransactionErr::Plain(ERRL!( - "Payment {} has an invalid status of {} in the db", - payment_hex, - payment.status - ))) - }, - HTLCStatus::Succeeded => return Ok(TransactionEnum::LightningPayment(payment_hash)), - HTLCStatus::Failed => { - return Err(TransactionErr::Plain(ERRL!( - "Lightning swap payment {} failed", - payment_hex - ))) - }, - }, - Ok(None) => return Err(TransactionErr::Plain(ERRL!("Payment {} not found in DB", payment_hex))), - Err(e) => { + match self.db.get_payment_from_db(payment_hash).await { + Ok(Some(payment)) => match payment.status { + HTLCStatus::Pending => (), + HTLCStatus::Claimable => { return Err(TransactionErr::Plain(ERRL!( - "Error getting payment {} from db: {}", + "Payment {} has an invalid status of {} in the db", payment_hex, - e + payment.status ))) }, - } - - // note: When sleeping for only 1 second the test_send_payment_and_swaps unit test took 20 seconds to complete instead of 37 seconds when sleeping for 10 seconds - // Todo: In next sprints, should add a mutex for lightning swap payments to avoid overloading the shared db connection with requests when the sleep time is reduced and multiple swaps are ran together. - // Todo: The aim is to make lightning swap payments as fast as possible, more sleep time can be allowed for maker payment since it waits for the secret to be revealed on another chain first. - // Todo: Running swap payments statuses should be loaded from db on restarts in this case. - Timer::sleep(10.).await; + HTLCStatus::Succeeded => return Ok(TransactionEnum::LightningPayment(payment_hash)), + HTLCStatus::Failed => { + return Err(TransactionErr::Plain(ERRL!( + "Lightning swap payment {} failed", + payment_hex + ))) + }, + }, + Ok(None) => return Err(TransactionErr::Plain(ERRL!("Payment {} not found in DB", payment_hex))), + Err(e) => { + return Err(TransactionErr::Plain(ERRL!( + "Error getting payment {} from db: {}", + payment_hex, + e + ))) + }, } - }; - Box::new(fut.boxed().compat()) + + // note: When sleeping for only 1 second the test_send_payment_and_swaps unit test took 20 seconds to complete instead of 37 seconds when sleeping for 10 seconds + // Todo: In next sprints, should add a mutex for lightning swap payments to avoid overloading the shared db connection with requests when the sleep time is reduced and multiple swaps are ran together. + // Todo: The aim is to make lightning swap payments as fast as possible, more sleep time can be allowed for maker payment since it waits for the secret to be revealed on another chain first. + // Todo: Running swap payments statuses should be loaded from db on restarts in this case. + Timer::sleep(10.).await; + } } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { diff --git a/mm2src/coins/lightning/ln_platform.rs b/mm2src/coins/lightning/ln_platform.rs index 4d0573af95..a3a4c22776 100644 --- a/mm2src/coins/lightning/ln_platform.rs +++ b/mm2src/coins/lightning/ln_platform.rs @@ -1,8 +1,8 @@ use super::*; use crate::lightning::ln_errors::{SaveChannelClosingError, SaveChannelClosingResult}; -use crate::utxo::rpc_clients::{BestBlock as RpcBestBlock, BlockHashOrHeight, ConfirmedTransactionInfo, - ElectrumBlockHeader, ElectrumClient, ElectrumNonce, EstimateFeeMethod, - UtxoRpcClientEnum, UtxoRpcResult}; +use crate::lightning::ln_utils::RpcBestBlock; +use crate::utxo::rpc_clients::{BlockHashOrHeight, ConfirmedTransactionInfo, ElectrumBlockHeader, ElectrumClient, + ElectrumNonce, EstimateFeeMethod, UtxoRpcClientEnum, UtxoRpcResult}; use crate::utxo::spv::SimplePaymentVerification; use crate::utxo::utxo_standard::UtxoStandardCoin; use crate::utxo::GetConfirmedTxError; @@ -15,7 +15,7 @@ use bitcoin::hash_types::{BlockHash, TxMerkleNode, Txid}; use bitcoin_hashes::{sha256d, Hash}; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, SpawnFuture, Timer}; use common::log::{debug, error, info}; -use common::wait_until_sec; +use common::{block_on_f01, wait_until_sec}; use futures::compat::Future01CompatExt; use futures::future::join_all; use keys::hash::H256; @@ -542,7 +542,6 @@ impl Platform { check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, watcher_reward: false, }) - .compat() .await .map_to_mm(|e| SaveChannelClosingError::WaitForFundingTxSpendError(e.get_plain_text_format()))?; @@ -570,17 +569,15 @@ impl FeeEstimator for Platform { ConfirmationTarget::HighPriority => self.confirmations_targets.high_priority, }; let fee_per_kb = tokio::task::block_in_place(move || { - self.rpc_client() - .estimate_fee_sat( - platform_coin.decimals(), - // Todo: when implementing Native client detect_fee_method should be used for Native and - // EstimateFeeMethod::Standard for Electrum - &EstimateFeeMethod::Standard, - &conf.estimate_fee_mode, - n_blocks, - ) - .wait() - .unwrap_or(latest_fees) + block_on_f01(self.rpc_client().estimate_fee_sat( + platform_coin.decimals(), + // Todo: when implementing Native client detect_fee_method should be used for Native and + // EstimateFeeMethod::Standard for Electrum + &EstimateFeeMethod::Standard, + &conf.estimate_fee_mode, + n_blocks, + )) + .unwrap_or(latest_fees) }); // Set default fee to last known fee for the corresponding confirmation target diff --git a/mm2src/coins/lightning/ln_sql.rs b/mm2src/coins/lightning/ln_sql.rs index 4b5dd2f7c8..0062479ebf 100644 --- a/mm2src/coins/lightning/ln_sql.rs +++ b/mm2src/coins/lightning/ln_sql.rs @@ -1448,6 +1448,7 @@ mod tests { let expected_payments = if expected_payments_vec.len() > 10 { expected_payments_vec[..10].to_vec() } else { + #[allow(clippy::redundant_clone)] // This is a false-possitive bug from clippy expected_payments_vec.clone() }; let actual_payments = result.payments; @@ -1611,6 +1612,7 @@ mod tests { let expected_channels = if expected_channels_vec.len() > 10 { expected_channels_vec[..10].to_vec() } else { + #[allow(clippy::redundant_clone)] // This is a false-possitive bug from clippy expected_channels_vec.clone() }; let actual_channels = result.channels; diff --git a/mm2src/coins/lightning/ln_utils.rs b/mm2src/coins/lightning/ln_utils.rs index 693b7c3a4f..79868908fa 100644 --- a/mm2src/coins/lightning/ln_utils.rs +++ b/mm2src/coins/lightning/ln_utils.rs @@ -3,7 +3,7 @@ use crate::lightning::ln_db::LightningDB; use crate::lightning::ln_platform::{get_best_header, ln_best_block_update_loop, update_best_block}; use crate::lightning::ln_sql::SqliteLightningDB; use crate::lightning::ln_storage::{LightningStorage, NodesAddressesMap}; -use crate::utxo::rpc_clients::BestBlock as RpcBestBlock; +use crate::utxo::rpc_clients::ElectrumBlockHeader; use bitcoin::hash_types::BlockHash; use bitcoin_hashes::{sha256d, Hash}; use common::executor::SpawnFuture; @@ -38,6 +38,21 @@ pub type ChainMonitor = chainmonitor::ChainMonitor< pub type ChannelManager = SimpleArcChannelManager; pub type Router = DefaultRouter, Arc, Arc>; +#[derive(Debug, PartialEq)] +pub struct RpcBestBlock { + pub height: u64, + pub hash: H256Json, +} + +impl From for RpcBestBlock { + fn from(block_header: ElectrumBlockHeader) -> Self { + RpcBestBlock { + height: block_header.block_height(), + hash: block_header.block_hash(), + } + } +} + #[inline] fn ln_data_dir(ctx: &MmArc, ticker: &str) -> PathBuf { ctx.dbdir().join("LIGHTNING").join(ticker) } @@ -72,6 +87,7 @@ pub async fn init_db(ctx: &MmArc, ticker: String) -> EnableLightningResult + Send>; pub type TransactionResult = Result; @@ -683,6 +660,10 @@ impl TransactionErr { } } +impl std::fmt::Display for TransactionErr { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.get_plain_text_format()) } +} + #[derive(Debug, PartialEq)] pub enum FoundSwapTxSpend { Spent(TransactionEnum), @@ -857,7 +838,10 @@ pub enum SwapTxTypeWithSecretHash<'a> { taker_secret_hash: &'a [u8], }, /// Taker payment v2 - TakerPaymentV2 { maker_secret_hash: &'a [u8] }, + TakerPaymentV2 { + maker_secret_hash: &'a [u8], + taker_secret_hash: &'a [u8], + }, } impl<'a> SwapTxTypeWithSecretHash<'a> { @@ -879,7 +863,7 @@ impl<'a> SwapTxTypeWithSecretHash<'a> { my_public, other_public, ), - SwapTxTypeWithSecretHash::TakerPaymentV2 { maker_secret_hash } => { + SwapTxTypeWithSecretHash::TakerPaymentV2 { maker_secret_hash, .. } => { swap_proto_v2_scripts::taker_payment_script(time_lock, maker_secret_hash, my_public, other_public) }, } @@ -893,7 +877,7 @@ impl<'a> SwapTxTypeWithSecretHash<'a> { maker_secret_hash, taker_secret_hash, } => [*maker_secret_hash, *taker_secret_hash].concat(), - SwapTxTypeWithSecretHash::TakerPaymentV2 { maker_secret_hash } => maker_secret_hash.to_vec(), + SwapTxTypeWithSecretHash::TakerPaymentV2 { maker_secret_hash, .. } => maker_secret_hash.to_vec(), } } } @@ -957,6 +941,32 @@ pub struct RefundPaymentArgs<'a> { pub watcher_reward: bool, } +#[derive(Debug)] +pub struct RefundMakerPaymentTimelockArgs<'a> { + pub payment_tx: &'a [u8], + pub time_lock: u64, + pub taker_pub: &'a [u8], + pub tx_type_with_secret_hash: SwapTxTypeWithSecretHash<'a>, + pub swap_unique_data: &'a [u8], + pub watcher_reward: bool, + pub amount: BigDecimal, +} + +#[derive(Debug)] +pub struct RefundTakerPaymentArgs<'a> { + pub payment_tx: &'a [u8], + pub time_lock: u64, + pub maker_pub: &'a [u8], + pub tx_type_with_secret_hash: SwapTxTypeWithSecretHash<'a>, + pub swap_unique_data: &'a [u8], + pub watcher_reward: bool, + pub dex_fee: &'a DexFee, + /// Additional reward for maker (premium) + pub premium_amount: BigDecimal, + /// Actual volume of taker's payment + pub trading_amount: BigDecimal, +} + /// Helper struct wrapping arguments for [SwapOps::check_if_my_payment_sent]. #[derive(Clone, Debug)] pub struct CheckIfMyPaymentSentArgs<'a> { @@ -1063,11 +1073,11 @@ pub enum WatcherRewardError { /// Swap operations (mostly based on the Hash/Time locked transactions implemented by coin wallets). #[async_trait] pub trait SwapOps { - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionFut; + async fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionResult; - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut; + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult; - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionFut; + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult; async fn send_maker_spends_taker_payment( &self, @@ -1083,16 +1093,16 @@ pub trait SwapOps { async fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult; - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()>; + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()>; async fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()>; async fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()>; - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, - ) -> Box, Error = String> + Send>; + ) -> Result, String>; async fn search_for_swap_tx_spend_my( &self, @@ -1116,14 +1126,13 @@ pub trait SwapOps { /// Whether the refund transaction can be sent now /// For example: there are no additional conditions for ETH, but for some UTXO coins we should wait for /// locktime < MTP - fn can_refund_htlc(&self, locktime: u64) -> Box + Send + '_> { + async fn can_refund_htlc(&self, locktime: u64) -> Result { let now = now_sec(); - let result = if now > locktime { - CanRefundHtlc::CanRefundNow + if now > locktime { + Ok(CanRefundHtlc::CanRefundNow) } else { - CanRefundHtlc::HaveToWait(locktime - now + 1) - }; - Box::new(futures01::future::ok(result)) + Ok(CanRefundHtlc::HaveToWait(locktime - now + 1)) + } } /// Whether the swap payment is refunded automatically or not when the locktime expires, or the other side fails the HTLC. @@ -1251,10 +1260,16 @@ pub trait WatcherOps { /// Helper struct wrapping arguments for [TakerCoinSwapOpsV2::send_taker_funding] pub struct SendTakerFundingArgs<'a> { - /// Taker will be able to refund the payment after this timestamp - pub time_lock: u64, - /// The hash of the secret generated by taker, this needs to be revealed for immediate refund + /// For UTXO-based coins, the taker can refund the funding after this timestamp if the maker hasn't claimed it. + /// For smart contracts, the taker can refund the payment after this timestamp if the maker hasn't pre-approved the transaction. + /// This field is additionally used to wait for confirmations of ERC20 approval transaction. + pub funding_time_lock: u64, + /// For smart contracts, the taker can refund the payment after this timestamp if the maker hasn't claimed it by revealing their secret. + pub payment_time_lock: u64, + /// The hash of the secret generated by the taker, needed for immediate refund pub taker_secret_hash: &'a [u8], + /// The hash of the secret generated by the maker, needed to spend the payment + pub maker_secret_hash: &'a [u8], /// Maker's pubkey pub maker_pub: &'a [u8], /// DEX fee @@ -1270,11 +1285,17 @@ pub struct SendTakerFundingArgs<'a> { /// Helper struct wrapping arguments for [TakerCoinSwapOpsV2::refund_taker_funding_secret] pub struct RefundFundingSecretArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { pub funding_tx: &'a Coin::Tx, - pub time_lock: u64, + pub funding_time_lock: u64, + pub payment_time_lock: u64, pub maker_pubkey: &'a Coin::Pubkey, pub taker_secret: &'a [u8], pub taker_secret_hash: &'a [u8], - pub swap_contract_address: &'a Option, + pub maker_secret_hash: &'a [u8], + pub dex_fee: &'a DexFee, + /// Additional reward for maker (premium) + pub premium_amount: BigDecimal, + /// Actual volume of taker's payment + pub trading_amount: BigDecimal, pub swap_unique_data: &'a [u8], pub watcher_reward: bool, } @@ -1301,12 +1322,18 @@ pub struct GenTakerFundingSpendArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { pub struct ValidateTakerFundingArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { /// Taker funding transaction pub funding_tx: &'a Coin::Tx, - /// Taker will be able to refund the payment after this timestamp - pub time_lock: u64, + /// In EVM case: The timestamp after which the taker can refund the funding transaction if the taker hasn't pre-approved the transaction + /// In UTXO case: Taker will be able to refund the payment after this timestamp + pub funding_time_lock: u64, + /// In EVM case: The timestamp after which the taker can refund the payment transaction if the maker hasn't claimed it by revealing their secret. + /// UTXO doesn't use it + pub payment_time_lock: u64, /// The hash of the secret generated by taker pub taker_secret_hash: &'a [u8], + /// The hash of the secret generated by maker + pub maker_secret_hash: &'a [u8], /// Taker's pubkey - pub other_pub: &'a Coin::Pubkey, + pub taker_pub: &'a Coin::Pubkey, /// DEX fee amount pub dex_fee: &'a DexFee, /// Additional reward for maker (premium) @@ -1387,23 +1414,36 @@ impl From for TxGenError { } /// Enum covering error cases that can happen during swap v2 transaction validation. -#[derive(Debug, Display)] +#[derive(Debug, Display, EnumFromStringify)] pub enum ValidateSwapV2TxError { /// Payment sent to wrong address or has invalid amount. InvalidDestinationOrAmount(String), /// Error during conversion of BigDecimal amount to coin's specific monetary units (satoshis, wei, etc.). NumConversion(String), /// RPC error. + #[from_stringify("web3::Error")] Rpc(String), /// Serialized tx bytes don't match ones received from coin's RPC. #[display(fmt = "Tx bytes {:02x} don't match ones received from rpc {:02x}", actual, from_rpc)] - TxBytesMismatch { from_rpc: BytesJson, actual: BytesJson }, + TxBytesMismatch { + from_rpc: BytesJson, + actual: BytesJson, + }, /// Provided transaction doesn't have output with specific index TxLacksOfOutputs, - /// Input payment timelock overflows the type used by specific coin. - LocktimeOverflow(String), + /// Indicates that overflow occurred, either while calculating a total payment or converting the timelock. + Overflow(String), /// Internal error + #[from_stringify("ethabi::Error", "TryFromSliceError")] Internal(String), + /// Payment transaction is in unexpected state. E.g., `Uninitialized` instead of `PaymentSent` for ETH payment. + UnexpectedPaymentState(String), + /// Payment transaction doesn't exist on-chain. + TxDoesNotExist(String), + /// Transaction has wrong properties, for example, it has been sent to a wrong address. + WrongPaymentTx(String), + ProtocolNotSupported(String), + InvalidData(String), } impl From for ValidateSwapV2TxError { @@ -1414,6 +1454,33 @@ impl From for ValidateSwapV2TxError { fn from(err: UtxoRpcError) -> Self { ValidateSwapV2TxError::Rpc(err.to_string()) } } +impl From for ValidateSwapV2TxError { + fn from(err: PaymentStatusErr) -> Self { + match err { + PaymentStatusErr::Internal(e) => ValidateSwapV2TxError::Internal(e), + PaymentStatusErr::Transport(e) => ValidateSwapV2TxError::Rpc(e), + PaymentStatusErr::ABIError(e) | PaymentStatusErr::InvalidData(e) => ValidateSwapV2TxError::InvalidData(e), + } + } +} + +impl From for ValidateSwapV2TxError { + fn from(err: ValidatePaymentV2Err) -> Self { + match err { + ValidatePaymentV2Err::UnexpectedPaymentState(e) => ValidateSwapV2TxError::UnexpectedPaymentState(e), + ValidatePaymentV2Err::WrongPaymentTx(e) => ValidateSwapV2TxError::WrongPaymentTx(e), + } + } +} + +impl From for ValidateSwapV2TxError { + fn from(err: PrepareTxDataError) -> Self { + match err { + PrepareTxDataError::ABIError(e) | PrepareTxDataError::Internal(e) => ValidateSwapV2TxError::Internal(e), + } + } +} + /// Enum covering error cases that can happen during taker funding spend preimage validation. #[derive(Debug, Display, EnumFromStringify)] pub enum ValidateTakerFundingSpendPreimageError { @@ -1533,8 +1600,6 @@ pub struct NftSwapInfo<'a, Coin: ParseNftAssocTypes + ?Sized> { pub token_id: &'a [u8], /// The type of smart contract that governs this NFT pub contract_type: &'a Coin::ContractType, - /// Etomic swap contract address - pub swap_contract_address: &'a Coin::ContractAddress, } pub struct SendNftMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ParseNftAssocTypes + ?Sized> { @@ -1592,7 +1657,7 @@ pub struct ValidateNftMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ParseNftA pub nft_swap_info: &'a NftSwapInfo<'a, Coin>, } -pub struct RefundMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { +pub struct RefundMakerPaymentSecretArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { /// Maker payment tx pub maker_payment_tx: &'a Coin::Tx, /// Maker will be able to refund the payment after this timestamp @@ -1607,6 +1672,24 @@ pub struct RefundMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { pub taker_pub: &'a Coin::Pubkey, /// Unique data of specific swap pub swap_unique_data: &'a [u8], + pub amount: BigDecimal, +} + +/// Common refund NFT Maker Payment structure for [MakerNftSwapOpsV2::refund_nft_maker_payment_v2_timelock] and +/// [MakerNftSwapOpsV2::refund_nft_maker_payment_v2_secret] methods +pub struct RefundNftMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ParseNftAssocTypes + ?Sized> { + /// Maker payment tx + pub maker_payment_tx: &'a Coin::Tx, + /// The hash of the secret generated by taker, this is used for immediate refund + pub taker_secret_hash: &'a [u8], + /// The hash of the secret generated by maker, taker needs it to spend the payment + pub maker_secret_hash: &'a [u8], + /// Taker's secret + pub taker_secret: &'a [u8], + /// Unique data of specific swap + pub swap_unique_data: &'a [u8], + /// The type of smart contract that governs this NFT + pub contract_type: &'a Coin::ContractType, } pub struct SpendMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { @@ -1624,13 +1707,12 @@ pub struct SpendMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { pub maker_pub: &'a Coin::Pubkey, /// Unique data of specific swap pub swap_unique_data: &'a [u8], + pub amount: BigDecimal, } pub struct SpendNftMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ParseNftAssocTypes + ?Sized> { /// Maker payment tx pub maker_payment_tx: &'a Coin::Tx, - /// Maker will be able to refund the payment after this timestamp - pub time_lock: u64, /// The hash of the secret generated by taker, this is used for immediate refund pub taker_secret_hash: &'a [u8], /// The hash of the secret generated by maker, taker needs it to spend the payment @@ -1643,13 +1725,11 @@ pub struct SpendNftMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ParseNftAsso pub swap_unique_data: &'a [u8], /// The type of smart contract that governs this NFT pub contract_type: &'a Coin::ContractType, - /// Etomic swap contract address - pub swap_contract_address: &'a Coin::ContractAddress, } /// Operations specific to maker coin in [Trading Protocol Upgrade implementation](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895) #[async_trait] -pub trait MakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { +pub trait MakerCoinSwapOpsV2: ParseCoinAssocTypes + CommonSwapOpsV2 + Send + Sync + 'static { /// Generate and broadcast maker payment transaction async fn send_maker_payment_v2(&self, args: SendMakerPaymentArgs<'_, Self>) -> Result; @@ -1657,12 +1737,15 @@ pub trait MakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { async fn validate_maker_payment_v2(&self, args: ValidateMakerPaymentArgs<'_, Self>) -> ValidatePaymentResult<()>; /// Refund maker payment transaction using timelock path - async fn refund_maker_payment_v2_timelock(&self, args: RefundPaymentArgs<'_>) -> Result; + async fn refund_maker_payment_v2_timelock( + &self, + args: RefundMakerPaymentTimelockArgs<'_>, + ) -> Result; /// Refund maker payment transaction using immediate refund path async fn refund_maker_payment_v2_secret( &self, - args: RefundMakerPaymentArgs<'_, Self>, + args: RefundMakerPaymentSecretArgs<'_, Self>, ) -> Result; /// Spend maker payment transaction @@ -1691,19 +1774,19 @@ pub trait MakerNftSwapOpsV2: ParseCoinAssocTypes + ParseNftAssocTypes + Send + S /// Refund NFT maker payment transaction using timelock path async fn refund_nft_maker_payment_v2_timelock( &self, - args: RefundPaymentArgs<'_>, + args: RefundNftMakerPaymentArgs<'_, Self>, ) -> Result; /// Refund NFT maker payment transaction using immediate refund path async fn refund_nft_maker_payment_v2_secret( &self, - args: RefundMakerPaymentArgs<'_, Self>, + args: RefundNftMakerPaymentArgs<'_, Self>, ) -> Result; } /// Enum representing errors that can occur while waiting for taker payment spend. -#[derive(Display)] -pub enum WaitForTakerPaymentSpendError { +#[derive(Display, Debug, EnumFromStringify)] +pub enum WaitForPaymentSpendError { /// Timeout error variant, indicating that the wait for taker payment spend has timed out. #[display( fmt = "Timed out waiting for taker payment spend, wait_until {}, now {}", @@ -1716,24 +1799,47 @@ pub enum WaitForTakerPaymentSpendError { /// The current timestamp when the timeout occurred. now: u64, }, - /// Invalid input transaction error variant, containing additional information about the error. InvalidInputTx(String), + Internal(String), + #[from_stringify("ethabi::Error")] + #[display(fmt = "ABI error: {}", _0)] + ABIError(String), + InvalidData(String), + Transport(String), } -impl From for WaitForTakerPaymentSpendError { +impl From for WaitForPaymentSpendError { fn from(err: WaitForOutputSpendErr) -> Self { match err { - WaitForOutputSpendErr::Timeout { wait_until, now } => { - WaitForTakerPaymentSpendError::Timeout { wait_until, now } - }, + WaitForOutputSpendErr::Timeout { wait_until, now } => WaitForPaymentSpendError::Timeout { wait_until, now }, WaitForOutputSpendErr::NoOutputWithIndex(index) => { - WaitForTakerPaymentSpendError::InvalidInputTx(format!("Tx doesn't have output with index {}", index)) + WaitForPaymentSpendError::InvalidInputTx(format!("Tx doesn't have output with index {}", index)) }, } } } +impl From for WaitForPaymentSpendError { + fn from(e: PaymentStatusErr) -> Self { + match e { + PaymentStatusErr::ABIError(e) => Self::ABIError(e), + PaymentStatusErr::Transport(e) => Self::Transport(e), + PaymentStatusErr::Internal(e) => Self::Internal(e), + PaymentStatusErr::InvalidData(e) => Self::InvalidData(e), + } + } +} + +impl From for WaitForPaymentSpendError { + fn from(e: PrepareTxDataError) -> Self { + match e { + PrepareTxDataError::ABIError(e) => Self::ABIError(e), + PrepareTxDataError::Internal(e) => Self::Internal(e), + } + } +} + /// Enum representing different ways a funding transaction can be spent. /// /// This enum is generic over types that implement the `ParseCoinAssocTypes` trait. @@ -1779,11 +1885,12 @@ pub enum SearchForFundingSpendErr { Rpc(String), /// Variant indicating an error during conversion of the `from_block` argument with associated `TryFromIntError`. FromBlockConversionErr(TryFromIntError), + Internal(String), } /// Operations specific to taker coin in [Trading Protocol Upgrade implementation](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895) #[async_trait] -pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { +pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + CommonSwapOpsV2 + Send + Sync + 'static { /// Generate and broadcast taker funding transaction that includes dex fee, maker premium and actual trading volume. /// Funding tx can be reclaimed immediately if maker back-outs (doesn't send maker payment) async fn send_taker_funding(&self, args: SendTakerFundingArgs<'_>) -> Result; @@ -1792,7 +1899,8 @@ pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateSwapV2TxResult; /// Refunds taker funding transaction using time-locked path without secret reveal. - async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> Result; + async fn refund_taker_funding_timelock(&self, args: RefundTakerPaymentArgs<'_>) + -> Result; /// Reclaims taker funding transaction using immediate refund path with secret reveal. async fn refund_taker_funding_secret( @@ -1822,7 +1930,7 @@ pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { preimage: &TxPreimageWithSig, ) -> ValidateTakerFundingSpendPreimageResult; - /// Generates and signs a preimage spending funding tx to the combined taker payment + /// Sign and send a spending funding tx to the combined taker payment async fn sign_and_send_taker_funding_spend( &self, preimage: &TxPreimageWithSig, @@ -1831,7 +1939,8 @@ pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { ) -> Result; /// Refunds taker payment transaction. - async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> Result; + async fn refund_combined_taker_payment(&self, args: RefundTakerPaymentArgs<'_>) + -> Result; /// Generates and signs taker payment spend preimage. The preimage and signature should be /// shared with maker to proceed with protocol execution. @@ -1863,10 +1972,15 @@ pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { taker_payment: &Self::Tx, from_block: u64, wait_until: u64, - ) -> MmResult; + ) -> MmResult; +} +#[async_trait] +pub trait CommonSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { /// Derives an HTLC key-pair and returns a public key corresponding to that key. fn derive_htlc_pubkey_v2(&self, swap_unique_data: &[u8]) -> Self::Pubkey; + + fn derive_htlc_pubkey_v2_bytes(&self, swap_unique_data: &[u8]) -> Vec; } /// Operations that coins have independently from the MarketMaker. @@ -1917,7 +2031,13 @@ pub trait MarketCoinOps { fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send>; - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut; + /// Waits for spending/unlocking of funds locked in a HTLC construction specific to the coin's + /// chain. Implementation should monitor locked funds (UTXO/contract/etc.) until funds are + /// spent/unlocked or timeout is reached. + /// + /// Returns spending tx/event from mempool/pending state to allow prompt extraction of preimage + /// secret. + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult; fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result>; @@ -2101,13 +2221,6 @@ pub enum TxFeeDetails { Qrc20(Qrc20FeeDetails), Slp(SlpFeeDetails), Tendermint(TendermintFeeDetails), - #[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") - ))] - Solana(SolanaFeeDetails), } /// Deserialize the TxFeeDetails as an untagged enum. @@ -2122,13 +2235,6 @@ impl<'de> Deserialize<'de> for TxFeeDetails { Utxo(UtxoFeeDetails), Eth(EthTxFeeDetails), Qrc20(Qrc20FeeDetails), - #[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") - ))] - Solana(SolanaFeeDetails), Tendermint(TendermintFeeDetails), } @@ -2136,13 +2242,6 @@ impl<'de> Deserialize<'de> for TxFeeDetails { TxFeeDetailsUnTagged::Utxo(f) => Ok(TxFeeDetails::Utxo(f)), TxFeeDetailsUnTagged::Eth(f) => Ok(TxFeeDetails::Eth(f)), TxFeeDetailsUnTagged::Qrc20(f) => Ok(TxFeeDetails::Qrc20(f)), - #[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") - ))] - TxFeeDetailsUnTagged::Solana(f) => Ok(TxFeeDetails::Solana(f)), TxFeeDetailsUnTagged::Tendermint(f) => Ok(TxFeeDetails::Tendermint(f)), } } @@ -2160,16 +2259,6 @@ impl From for TxFeeDetails { fn from(qrc20_details: Qrc20FeeDetails) -> Self { TxFeeDetails::Qrc20(qrc20_details) } } -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -impl From for TxFeeDetails { - fn from(solana_details: SolanaFeeDetails) -> Self { TxFeeDetails::Solana(solana_details) } -} - impl From for TxFeeDetails { fn from(tendermint_details: TendermintFeeDetails) -> Self { TxFeeDetails::Tendermint(tendermint_details) } } @@ -2406,8 +2495,9 @@ pub enum TradePreimageValue { UpperBound(BigDecimal), } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, Default)] pub enum SwapTxFeePolicy { + #[default] Unsupported, Internal, Low, @@ -2415,10 +2505,6 @@ pub enum SwapTxFeePolicy { High, } -impl Default for SwapTxFeePolicy { - fn default() -> Self { SwapTxFeePolicy::Unsupported } -} - #[derive(Debug, Deserialize)] pub struct SwapTxFeePolicyRequest { coin: String, @@ -2560,7 +2646,7 @@ pub enum BalanceError { UnexpectedDerivationMethod(UnexpectedDerivationMethod), #[display(fmt = "Wallet storage error: {}", _0)] WalletStorageError(String), - #[from_stringify("Bip32Error", "NumConversError")] + #[from_stringify("Bip32Error", "NumConversError", "ParseBigIntError")] #[display(fmt = "Internal: {}", _0)] Internal(String), } @@ -2912,8 +2998,8 @@ pub enum WithdrawError { NotEnoughNftsAmount { token_address: String, token_id: String, - available: BigDecimal, - required: BigDecimal, + available: BigUint, + required: BigUint, }, #[display(fmt = "DB error {}", _0)] DbError(String), @@ -3194,6 +3280,10 @@ pub trait MmCoin: /// The coin can be initialized, but it cannot participate in the swaps. fn wallet_only(&self, ctx: &MmArc) -> bool { let coin_conf = coin_conf(ctx, self.ticker()); + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if coin_conf.is_null() { + return true; + } coin_conf["wallet_only"].as_bool().unwrap_or(false) } @@ -3377,20 +3467,6 @@ pub enum MmCoinEnum { SlpToken(SlpToken), Tendermint(TendermintCoin), TendermintToken(TendermintToken), - #[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") - ))] - SolanaCoin(SolanaCoin), - #[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") - ))] - SplToken(SplToken), #[cfg(not(target_arch = "wasm32"))] LightningCoin(LightningCoin), #[cfg(feature = "enable-sia")] @@ -3410,26 +3486,6 @@ impl From for MmCoinEnum { fn from(c: TestCoin) -> MmCoinEnum { MmCoinEnum::Test(c) } } -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -impl From for MmCoinEnum { - fn from(c: SolanaCoin) -> MmCoinEnum { MmCoinEnum::SolanaCoin(c) } -} - -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -impl From for MmCoinEnum { - fn from(c: SplToken) -> MmCoinEnum { MmCoinEnum::SplToken(c) } -} - impl From for MmCoinEnum { fn from(coin: QtumCoin) -> Self { MmCoinEnum::QtumCoin(coin) } } @@ -3487,20 +3543,6 @@ impl Deref for MmCoinEnum { #[cfg(feature = "enable-sia")] MmCoinEnum::SiaCoin(ref c) => c, MmCoinEnum::Test(ref c) => c, - #[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") - ))] - MmCoinEnum::SolanaCoin(ref c) => c, - #[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") - ))] - MmCoinEnum::SplToken(ref c) => c, } } } @@ -3536,7 +3578,7 @@ pub struct MmCoinStruct { } impl MmCoinStruct { - fn new(coin: MmCoinEnum) -> Self { + pub fn new(coin: MmCoinEnum) -> Self { Self { inner: coin, is_available: AtomicBool::new(true).into(), @@ -3809,19 +3851,19 @@ impl CoinsContext { async fn tx_history_db(&self) -> TxHistoryResult> { Ok(self.tx_history_db.get_or_initialize().await?) } + + #[inline(always)] + pub async fn lock_coins(&self) -> AsyncMutexGuard> { self.coins.lock().await } } /// This enum is used in coin activation requests. -#[derive(Copy, Clone, Debug, Deserialize, Serialize)] +#[derive(Copy, Clone, Debug, Deserialize, Serialize, Default)] pub enum PrivKeyActivationPolicy { + #[default] ContextPrivKey, Trezor, } -impl Default for PrivKeyActivationPolicy { - fn default() -> Self { PrivKeyActivationPolicy::ContextPrivKey } -} - impl PrivKeyActivationPolicy { pub fn is_hw_policy(&self) -> bool { matches!(self, PrivKeyActivationPolicy::Trezor) } } @@ -4210,14 +4252,6 @@ pub enum CoinProtocol { network: BlockchainNetwork, confirmation_targets: PlatformCoinConfirmationTargets, }, - #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] - SOLANA, - #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] - SPLTOKEN { - platform: String, - token_contract_address: String, - decimals: u8, - }, ZHTLC(ZcoinProtocolInfo), #[cfg(feature = "enable-sia")] SIA, @@ -4226,9 +4260,101 @@ pub enum CoinProtocol { }, } -pub type RpcTransportEventHandlerShared = Arc; +#[derive(Clone, Debug, Deserialize, Display, PartialEq, Serialize)] +pub enum CustomTokenError { + #[display( + fmt = "Token with the same ticker already exists in coins configs, ticker in config: {}", + ticker_in_config + )] + DuplicateTickerInConfig { ticker_in_config: String }, + #[display( + fmt = "Token with the same contract address already exists in coins configs, ticker in config: {}", + ticker_in_config + )] + DuplicateContractInConfig { ticker_in_config: String }, + #[display( + fmt = "Token is already activated, ticker: {}, contract address: {}", + ticker, + contract_address + )] + TokenWithSameContractAlreadyActivated { ticker: String, contract_address: String }, +} -/// Common methods to measure the outgoing requests and incoming responses statistics. +impl CoinProtocol { + /// Returns the platform coin associated with the coin protocol, if any. + pub fn platform(&self) -> Option<&str> { + match self { + CoinProtocol::QRC20 { platform, .. } + | CoinProtocol::ERC20 { platform, .. } + | CoinProtocol::SLPTOKEN { platform, .. } + | CoinProtocol::NFT { platform, .. } => Some(platform), + CoinProtocol::TENDERMINTTOKEN(info) => Some(&info.platform), + #[cfg(not(target_arch = "wasm32"))] + CoinProtocol::LIGHTNING { platform, .. } => Some(platform), + CoinProtocol::UTXO + | CoinProtocol::QTUM + | CoinProtocol::ETH + | CoinProtocol::BCH { .. } + | CoinProtocol::TENDERMINT(_) + | CoinProtocol::ZHTLC(_) => None, + #[cfg(feature = "enable-sia")] + CoinProtocol::SIA => None, + } + } + + /// Returns the contract address associated with the coin, if any. + pub fn contract_address(&self) -> Option<&str> { + match self { + CoinProtocol::QRC20 { contract_address, .. } | CoinProtocol::ERC20 { contract_address, .. } => { + Some(contract_address) + }, + CoinProtocol::SLPTOKEN { .. } + | CoinProtocol::UTXO + | CoinProtocol::QTUM + | CoinProtocol::ETH + | CoinProtocol::BCH { .. } + | CoinProtocol::TENDERMINT(_) + | CoinProtocol::TENDERMINTTOKEN(_) + | CoinProtocol::ZHTLC(_) + | CoinProtocol::NFT { .. } => None, + #[cfg(not(target_arch = "wasm32"))] + CoinProtocol::LIGHTNING { .. } => None, + #[cfg(feature = "enable-sia")] + CoinProtocol::SIA => None, + } + } + + /// Several checks to be preformed when a custom token is being activated to check uniqueness among other things. + #[allow(clippy::result_large_err)] + pub fn custom_token_validations(&self, ctx: &MmArc) -> MmResult<(), CustomTokenError> { + let CoinProtocol::ERC20 { + platform, + contract_address, + } = self + else { + return Ok(()); + }; + + // Check if there is a token with the same contract address in the config. + // If there is, return an error as the user should use this token instead of activating a custom one. + // This is necessary as we will create an orderbook for this custom token using the contract address, + // if it is duplicated in config, we will have two orderbooks one using the ticker and one using the contract address. + // Todo: We should use the contract address for orderbook topics instead of the ticker once we make custom tokens non-wallet only. + // If a coin is added to the config later, users who added it as a custom token and did not update will not see the orderbook. + if let Some(existing_ticker) = get_erc20_ticker_by_contract_address(ctx, platform, contract_address) { + return Err(MmError::new(CustomTokenError::DuplicateContractInConfig { + ticker_in_config: existing_ticker, + })); + } + + Ok(()) + } +} + +/// Common methods to handle the connection events. +/// +/// Note that the handler methods are sync and shouldn't take long time executing, otherwise it will hurt the performance. +/// If a handler needs to do some heavy work, it should be spawned/done in a separate thread. pub trait RpcTransportEventHandler { fn debug_info(&self) -> String; @@ -4236,12 +4362,15 @@ pub trait RpcTransportEventHandler { fn on_incoming_response(&self, data: &[u8]); - fn on_connected(&self, address: String) -> Result<(), String>; + fn on_connected(&self, address: &str) -> Result<(), String>; - fn on_disconnected(&self, address: String) -> Result<(), String>; + fn on_disconnected(&self, address: &str) -> Result<(), String>; } -impl fmt::Debug for dyn RpcTransportEventHandler + Send + Sync { +pub type SharableRpcTransportEventHandler = dyn RpcTransportEventHandler + Send + Sync; +pub type RpcTransportEventHandlerShared = Arc; + +impl fmt::Debug for SharableRpcTransportEventHandler { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.debug_info()) } } @@ -4252,9 +4381,21 @@ impl RpcTransportEventHandler for RpcTransportEventHandlerShared { fn on_incoming_response(&self, data: &[u8]) { self.as_ref().on_incoming_response(data) } - fn on_connected(&self, address: String) -> Result<(), String> { self.as_ref().on_connected(address) } + fn on_connected(&self, address: &str) -> Result<(), String> { self.as_ref().on_connected(address) } + + fn on_disconnected(&self, address: &str) -> Result<(), String> { self.as_ref().on_disconnected(address) } +} + +impl RpcTransportEventHandler for Box { + fn debug_info(&self) -> String { self.as_ref().debug_info() } + + fn on_outgoing_request(&self, data: &[u8]) { self.as_ref().on_outgoing_request(data) } + + fn on_incoming_response(&self, data: &[u8]) { self.as_ref().on_incoming_response(data) } + + fn on_connected(&self, address: &str) -> Result<(), String> { self.as_ref().on_connected(address) } - fn on_disconnected(&self, address: String) -> Result<(), String> { self.as_ref().on_disconnected(address) } + fn on_disconnected(&self, address: &str) -> Result<(), String> { self.as_ref().on_disconnected(address) } } impl RpcTransportEventHandler for Vec { @@ -4275,16 +4416,28 @@ impl RpcTransportEventHandler for Vec { } } - fn on_connected(&self, address: String) -> Result<(), String> { + fn on_connected(&self, address: &str) -> Result<(), String> { + let mut errors = vec![]; for handler in self { - try_s!(handler.on_connected(address.clone())) + if let Err(e) = handler.on_connected(address) { + errors.push((handler.debug_info(), e)) + } + } + if !errors.is_empty() { + return Err(format!("Errors: {:?}", errors)); } Ok(()) } - fn on_disconnected(&self, address: String) -> Result<(), String> { + fn on_disconnected(&self, address: &str) -> Result<(), String> { + let mut errors = vec![]; for handler in self { - try_s!(handler.on_disconnected(address.clone())) + if let Err(e) = handler.on_disconnected(address) { + errors.push((handler.debug_info(), e)) + } + } + if !errors.is_empty() { + return Err(format!("Errors: {:?}", errors)); } Ok(()) } @@ -4345,17 +4498,9 @@ impl RpcTransportEventHandler for CoinTransportMetrics { "coin" => self.ticker.to_owned(), "client" => self.client.to_owned()); } - fn on_connected(&self, _address: String) -> Result<(), String> { - // Handle a new connected endpoint if necessary. - // Now just return the Ok - Ok(()) - } + fn on_connected(&self, _address: &str) -> Result<(), String> { Ok(()) } - fn on_disconnected(&self, _address: String) -> Result<(), String> { - // Handle disconnected endpoint if necessary. - // Now just return the Ok - Ok(()) - } + fn on_disconnected(&self, _address: &str) -> Result<(), String> { Ok(()) } } #[async_trait] @@ -4378,10 +4523,20 @@ pub fn coin_conf(ctx: &MmArc, ticker: &str) -> Json { } } -pub fn is_wallet_only_conf(conf: &Json) -> bool { conf["wallet_only"].as_bool().unwrap_or(false) } +pub fn is_wallet_only_conf(conf: &Json) -> bool { + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if conf.is_null() { + return true; + } + conf["wallet_only"].as_bool().unwrap_or(false) +} pub fn is_wallet_only_ticker(ctx: &MmArc, ticker: &str) -> bool { let coin_conf = coin_conf(ctx, ticker); + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if coin_conf.is_null() { + return true; + } coin_conf["wallet_only"].as_bool().unwrap_or(false) } @@ -4479,14 +4634,6 @@ pub async fn lp_coininit(ctx: &MmArc, ticker: &str, req: &Json) -> Result return ERR!("NFT protocol is not supported by lp_coininit"), #[cfg(not(target_arch = "wasm32"))] CoinProtocol::LIGHTNING { .. } => return ERR!("Lightning protocol is not supported by lp_coininit"), - #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] - CoinProtocol::SOLANA => { - return ERR!("Solana protocol is not supported by lp_coininit - use enable_solana_with_tokens instead") - }, - #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] - CoinProtocol::SPLTOKEN { .. } => { - return ERR!("SplToken protocol is not supported by lp_coininit - use enable_spl instead") - }, #[cfg(feature = "enable-sia")] CoinProtocol::SIA { .. } => { return ERR!("SIA protocol is not supported by lp_coininit. Use task::enable_sia::init"); @@ -4843,7 +4990,7 @@ pub async fn my_tx_history(ctx: MmArc, req: Json) -> Result>, S } /// `get_trade_fee` rpc implementation. -/// There is some consideration about this rpc: +/// There is some consideration about this rpc: /// for eth coin this rpc returns max possible trade fee (estimated for maximum possible gas limit for any kind of swap). /// However for eth coin, as part of fixing this issue https://github.com/KomodoPlatform/komodo-defi-framework/issues/1848, /// `max_taker_vol' and `trade_preimage` rpc now return more accurate required gas calculations. @@ -5071,10 +5218,6 @@ pub fn address_by_coin_conf_and_pubkey_str( CoinProtocol::LIGHTNING { .. } => { ERR!("address_by_coin_conf_and_pubkey_str is not implemented for lightning protocol yet!") }, - #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] - CoinProtocol::SOLANA | CoinProtocol::SPLTOKEN { .. } => { - ERR!("Solana pubkey is the public address - you do not need to use this rpc call.") - }, CoinProtocol::ZHTLC { .. } => ERR!("address_by_coin_conf_and_pubkey_str is not supported for ZHTLC protocol!"), #[cfg(feature = "enable-sia")] CoinProtocol::SIA { .. } => ERR!("address_by_coin_conf_and_pubkey_str is not supported for SIA protocol!"), // TODO Alright @@ -5429,7 +5572,6 @@ where // First, derive all empty addresses and put it into `balances` with default balance. let address_ids = (last_non_empty_address_id..checking_address_id) - .into_iter() .map(|address_id| HDAddressId { chain, address_id }); let empty_addresses = coin.derive_addresses(hd_account, address_ids) diff --git a/mm2src/coins/lp_price.rs b/mm2src/coins/lp_price.rs index b85708f361..fb339d3752 100644 --- a/mm2src/coins/lp_price.rs +++ b/mm2src/coins/lp_price.rs @@ -70,7 +70,7 @@ struct TickerInfos { change_24_h_provider: Provider, } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] pub enum Provider { #[serde(rename = "binance")] Binance, @@ -82,14 +82,13 @@ pub enum Provider { Forex, #[serde(rename = "nomics")] Nomics, + #[serde(rename = "testcoin")] + TestCoin, #[serde(rename = "unknown", other)] + #[default] Unknown, } -impl Default for Provider { - fn default() -> Self { Provider::Unknown } -} - #[derive(Default, Clone, Debug)] pub struct RateInfos { #[allow(dead_code)] diff --git a/mm2src/coins/my_tx_history_v2.rs b/mm2src/coins/my_tx_history_v2.rs index 20cfb9b8b1..f7348a5e78 100644 --- a/mm2src/coins/my_tx_history_v2.rs +++ b/mm2src/coins/my_tx_history_v2.rs @@ -264,17 +264,17 @@ impl<'a, Addr: Clone + DisplayAddress + Eq + std::hash::Hash, Tx: Transaction> T #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(tag = "type")] #[serde(rename_all = "snake_case")] +#[derive(Default)] pub enum MyTxHistoryTarget { + #[default] Iguana, - AccountId { account_id: u32 }, + AccountId { + account_id: u32, + }, AddressId(HDPathAccountToAddressId), AddressDerivationPath(StandardHDPath), } -impl Default for MyTxHistoryTarget { - fn default() -> Self { MyTxHistoryTarget::Iguana } -} - #[derive(Clone, Deserialize)] pub struct MyTxHistoryRequestV2 { pub(crate) coin: String, diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index 7002f97fbf..afc5f260a9 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -1,5 +1,8 @@ +use http::Uri; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::{MmError, MmResult}; +use mm2_p2p::p2p_ctx::P2PContext; +use proxy_signature::{ProxySign, RawMessage}; use url::Url; pub(crate) mod nft_errors; @@ -22,7 +25,10 @@ use crate::nft::nft_errors::{ClearNftDbError, MetaFromUrlError, ProtectFromSpamE use crate::nft::nft_structs::{build_nft_with_empty_meta, BuildNftFields, ClearNftDbReq, NftCommon, NftCtx, NftInfo, NftTransferCommon, PhishingDomainReq, PhishingDomainRes, RefreshMetadataReq, SpamContractReq, SpamContractRes, TransferMeta, TransferStatus, UriMeta}; +#[cfg(not(target_arch = "wasm32"))] +use crate::nft::storage::NftMigrationOps; use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps}; +use common::log::error; use common::parse_rfc3339_to_timestamp; use ethereum_types::{Address, H256}; use futures::compat::Future01CompatExt; @@ -44,7 +50,8 @@ use mm2_net::native_http::send_request_to_uri; #[cfg(target_arch = "wasm32")] use mm2_net::wasm::http::send_request_to_uri; -const MORALIS_API_ENDPOINT: &str = "api/v2"; +const MORALIS_API: &str = "api"; +const MORALIS_ENDPOINT_V: &str = "v2"; /// query parameters for moralis request: The format of the token ID const MORALIS_FORMAT_QUERY_NAME: &str = "format"; const MORALIS_FORMAT_QUERY_VALUE: &str = "decimal"; @@ -150,6 +157,9 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult MmResult<(), UpdateNftError> { let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; + let p2p_ctx = P2PContext::fetch_from_mm_arc(&ctx); let storage = nft_ctx.lock_db().await?; for chain in req.chains.iter() { let transfer_history_initialized = NftTransferHistoryStorageOps::is_initialized(&storage, chain).await?; let from_block = if transfer_history_initialized { + #[cfg(not(target_arch = "wasm32"))] + NftMigrationOps::migrate_tx_history_if_needed(&storage, chain).await?; let last_transfer_block = NftTransferHistoryStorageOps::get_last_block_number(&storage, chain).await?; last_transfer_block.map(|b| b + 1) } else { NftTransferHistoryStorageOps::init(&storage, chain).await?; None }; + // TODO activate and use global NFT instead of ETH coin after adding enable nft using coin conf support let coin_enum = lp_coinfind_or_err(&ctx, chain.to_ticker()).await?; let eth_coin = match coin_enum { MmCoinEnum::EthCoin(eth_coin) => eth_coin, @@ -233,26 +247,42 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft }) }, }; - let nft_transfers = get_moralis_nft_transfers(&ctx, chain, from_block, &req.url, eth_coin).await?; + let proxy_sign = if req.komodo_proxy { + let uri = Uri::from_str(req.url.as_ref()).map_err(|e| UpdateNftError::Internal(e.to_string()))?; + let proxy_sign = RawMessage::sign(p2p_ctx.keypair(), &uri, 0, common::PROXY_REQUEST_EXPIRATION_SEC) + .map_err(|e| UpdateNftError::Internal(e.to_string()))?; + Some(proxy_sign) + } else { + None + }; + + let wrapper = UrlSignWrapper { + chain, + orig_url: &req.url, + url_antispam: &req.url_antispam, + proxy_sign, + }; + + let nft_transfers = get_moralis_nft_transfers(&ctx, from_block, eth_coin, &wrapper).await?; storage.add_transfers_to_history(*chain, nft_transfers).await?; let nft_block = match NftListStorageOps::get_last_block_number(&storage, chain).await { Ok(Some(block)) => block, Ok(None) => { // if there are no rows in NFT LIST table we can try to get nft list from moralis. - let nft_list = cache_nfts_from_moralis(&ctx, &storage, chain, &req.url, &req.url_antispam).await?; + let nft_list = cache_nfts_from_moralis(&ctx, &storage, &wrapper).await?; update_meta_in_transfers(&storage, chain, nft_list).await?; - update_transfers_with_empty_meta(&storage, chain, &req.url, &req.url_antispam).await?; + update_transfers_with_empty_meta(&storage, &wrapper).await?; update_spam(&storage, *chain, &req.url_antispam).await?; update_phishing(&storage, chain, &req.url_antispam).await?; continue; }, Err(_) => { - // if there is an error, then NFT LIST table doesnt exist, so we need to cache nft list from moralis. + // if there is an error, then NFT LIST table doesn't exist, so we need to cache nft list from moralis. NftListStorageOps::init(&storage, chain).await?; - let nft_list = cache_nfts_from_moralis(&ctx, &storage, chain, &req.url, &req.url_antispam).await?; + let nft_list = cache_nfts_from_moralis(&ctx, &storage, &wrapper).await?; update_meta_in_transfers(&storage, chain, nft_list).await?; - update_transfers_with_empty_meta(&storage, chain, &req.url, &req.url_antispam).await?; + update_transfers_with_empty_meta(&storage, &wrapper).await?; update_spam(&storage, *chain, &req.url_antispam).await?; update_phishing(&storage, chain, &req.url_antispam).await?; continue; @@ -273,17 +303,9 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft last_nft_block: nft_block.to_string(), }); } - update_nft_list( - ctx.clone(), - &storage, - chain, - scanned_block + 1, - &req.url, - &req.url_antispam, - ) - .await?; + update_nft_list(ctx.clone(), &storage, scanned_block + 1, &wrapper).await?; update_nft_global_in_coins_ctx(&ctx, &storage, *chain).await?; - update_transfers_with_empty_meta(&storage, chain, &req.url, &req.url_antispam).await?; + update_transfers_with_empty_meta(&storage, &wrapper).await?; update_spam(&storage, *chain, &req.url_antispam).await?; update_phishing(&storage, chain, &req.url_antispam).await?; } @@ -299,7 +321,7 @@ where T: NftListStorageOps + NftTransferHistoryStorageOps, { let coins_ctx = CoinsContext::from_ctx(ctx).map_to_mm(UpdateNftError::Internal)?; - let mut coins = coins_ctx.coins.lock().await; + let mut coins = coins_ctx.lock_coins().await; let ticker = chain.to_nft_ticker(); if let Some(MmCoinStruct { @@ -454,18 +476,27 @@ fn prepare_uri_for_blocklist_endpoint( /// `possible_phishing` flags are set to true. pub async fn refresh_nft_metadata(ctx: MmArc, req: RefreshMetadataReq) -> MmResult<(), UpdateNftError> { let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; + let p2p_ctx = P2PContext::fetch_from_mm_arc(&ctx); let storage = nft_ctx.lock_db().await?; + + let proxy_sign = if req.komodo_proxy { + let uri = Uri::from_str(req.url.as_ref()).map_err(|e| UpdateNftError::Internal(e.to_string()))?; + let proxy_sign = RawMessage::sign(p2p_ctx.keypair(), &uri, 0, common::PROXY_REQUEST_EXPIRATION_SEC) + .map_err(|e| UpdateNftError::Internal(e.to_string()))?; + Some(proxy_sign) + } else { + None + }; + let wrapper = UrlSignWrapper { + chain: &req.chain, + orig_url: &req.url, + url_antispam: &req.url_antispam, + proxy_sign, + }; + let token_address_str = eth_addr_to_hex(&req.token_address); - let moralis_meta = match get_moralis_metadata( - token_address_str.clone(), - req.token_id.clone(), - &req.chain, - &req.url, - &req.url_antispam, - ) - .await - { + let mut moralis_meta = match get_moralis_metadata(token_address_str.clone(), req.token_id.clone(), &wrapper).await { Ok(moralis_meta) => moralis_meta, Err(_) => { storage @@ -486,10 +517,14 @@ pub async fn refresh_nft_metadata(ctx: MmArc, req: RefreshMetadataReq) -> MmResu })?; let token_uri = check_moralis_ipfs_bafy(moralis_meta.common.token_uri.as_deref()); let token_domain = get_domain_from_url(token_uri.as_deref()); + check_token_uri(&mut moralis_meta.common.possible_spam, token_uri.as_deref())?; + drop_mutability!(moralis_meta); let uri_meta = get_uri_meta( token_uri.as_deref(), moralis_meta.common.metadata.as_deref(), &req.url_antispam, + moralis_meta.common.possible_spam, + nft_db.possible_phishing, ) .await; // Gather domains for phishing checks @@ -600,23 +635,20 @@ where Ok(()) } -async fn get_moralis_nft_list( - ctx: &MmArc, - chain: &Chain, - url: &Url, - url_antispam: &Url, -) -> MmResult, GetNftInfoError> { +async fn get_moralis_nft_list(ctx: &MmArc, wrapper: &UrlSignWrapper<'_>) -> MmResult, GetNftInfoError> { let mut res_list = Vec::new(); + let chain = wrapper.chain; let ticker = chain.to_ticker(); let conf = coin_conf(ctx, ticker); let my_address = get_eth_address(ctx, &conf, ticker, &HDPathAccountToAddressId::default()).await?; - let uri_without_cursor = construct_moralis_uri_for_nft(url, &my_address.wallet_address, chain)?; + let uri_without_cursor = construct_moralis_uri_for_nft(wrapper.orig_url, &my_address.wallet_address, chain)?; // The cursor returned in the previous response (used for getting the next page). let mut cursor = String::new(); loop { + // Create a new URL instance from uri_without_cursor and modify its query to include the cursor if present let uri = format!("{}{}", uri_without_cursor, cursor); - let response = send_request_to_uri(uri.as_str()).await?; + let response = build_and_send_request(uri.as_str(), &wrapper.proxy_sign).await?; if let Some(nfts_list) = response["result"].as_array() { for nft_json in nfts_list { let nft_moralis = NftFromMoralis::deserialize(nft_json)?; @@ -624,7 +656,7 @@ async fn get_moralis_nft_list( Some(contract_type) => contract_type, None => continue, }; - let mut nft = build_nft_from_moralis(*chain, nft_moralis, contract_type, url_antispam).await; + let mut nft = build_nft_from_moralis(*chain, nft_moralis, contract_type, wrapper.url_antispam).await; protect_from_nft_spam_links(&mut nft, false)?; // collect NFTs from the page res_list.push(nft); @@ -632,7 +664,7 @@ async fn get_moralis_nft_list( // if cursor is not null, there are other NFTs on next page, // and we need to send new request with cursor to get info from the next page. if let Some(cursor_res) = response["cursor"].as_str() { - cursor = format!("{}{}", "&cursor=", cursor_res); + cursor = format!("&cursor={}", cursor_res); continue; } else { break; @@ -647,22 +679,24 @@ async fn get_moralis_nft_list( pub(crate) async fn get_nfts_for_activation( chain: &Chain, my_address: &Address, - url: &Url, + orig_url: &Url, + proxy_sign: Option, ) -> MmResult, GetNftInfoError> { let mut nfts_map = HashMap::new(); - let uri_without_cursor = construct_moralis_uri_for_nft(url, ð_addr_to_hex(my_address), chain)?; + let uri_without_cursor = construct_moralis_uri_for_nft(orig_url, ð_addr_to_hex(my_address), chain)?; // The cursor returned in the previous response (used for getting the next page). let mut cursor = String::new(); loop { + // Create a new URL instance from uri_without_cursor and modify its query to include the cursor if present let uri = format!("{}{}", uri_without_cursor, cursor); - let response = send_request_to_uri(uri.as_str()).await?; + let response = build_and_send_request(uri.as_str(), &proxy_sign).await?; if let Some(nfts_list) = response["result"].as_array() { process_nft_list_for_activation(nfts_list, chain, &mut nfts_map)?; // if cursor is not null, there are other NFTs on next page, // and we need to send new request with cursor to get info from the next page. if let Some(cursor_res) = response["cursor"].as_str() { - cursor = format!("{}{}", "&cursor=", cursor_res); + cursor = format!("&cursor={}", cursor_res); continue; } else { break; @@ -701,21 +735,22 @@ fn process_nft_list_for_activation( async fn get_moralis_nft_transfers( ctx: &MmArc, - chain: &Chain, from_block: Option, - url: &Url, eth_coin: EthCoin, + wrapper: &UrlSignWrapper<'_>, ) -> MmResult, GetNftInfoError> { + let chain = wrapper.chain; let mut res_list = Vec::new(); let ticker = chain.to_ticker(); let conf = coin_conf(ctx, ticker); let my_address = get_eth_address(ctx, &conf, ticker, &HDPathAccountToAddressId::default()).await?; - let mut uri_without_cursor = url.clone(); - uri_without_cursor.set_path(MORALIS_API_ENDPOINT); + let mut uri_without_cursor = wrapper.orig_url.clone(); uri_without_cursor .path_segments_mut() .map_to_mm(|_| GetNftInfoError::Internal("Invalid URI".to_string()))? + .push(MORALIS_API) + .push(MORALIS_ENDPOINT_V) .push(&my_address.wallet_address) .push("nft") .push("transfers"); @@ -734,14 +769,15 @@ async fn get_moralis_nft_transfers( let mut cursor = String::new(); let wallet_address = my_address.wallet_address; loop { + // Create a new URL instance from uri_without_cursor and modify its query to include the cursor if present let uri = format!("{}{}", uri_without_cursor, cursor); - let response = send_request_to_uri(uri.as_str()).await?; + let response = build_and_send_request(uri.as_str(), &wrapper.proxy_sign).await?; if let Some(transfer_list) = response["result"].as_array() { process_transfer_list(transfer_list, chain, wallet_address.as_str(), ð_coin, &mut res_list).await?; // if the cursor is not null, there are other NFTs transfers on next page, // and we need to send new request with cursor to get info from the next page. if let Some(cursor_res) = response["cursor"].as_str() { - cursor = format!("{}{}", "&cursor=", cursor_res); + cursor = format!("&cursor={}", cursor_res); continue; } else { break; @@ -857,18 +893,18 @@ async fn get_fee_details(eth_coin: &EthCoin, transaction_hash: &str) -> Option, ) -> MmResult { - let mut uri = url.clone(); - uri.set_path(MORALIS_API_ENDPOINT); + let mut uri = wrapper.orig_url.clone(); + let chain = wrapper.chain; uri.path_segments_mut() .map_to_mm(|_| GetNftInfoError::Internal("Invalid URI".to_string()))? + .push(MORALIS_API) + .push(MORALIS_ENDPOINT_V) .push("nft") .push(&token_address) .push(&token_id.to_string()); @@ -877,13 +913,13 @@ async fn get_moralis_metadata( .append_pair(MORALIS_FORMAT_QUERY_NAME, MORALIS_FORMAT_QUERY_VALUE); drop_mutability!(uri); - let response = send_request_to_uri(uri.as_str()).await?; + let response = build_and_send_request(uri.as_str(), &wrapper.proxy_sign).await?; let nft_moralis: NftFromMoralis = serde_json::from_str(&response.to_string())?; let contract_type = match nft_moralis.contract_type { Some(contract_type) => contract_type, None => return MmError::err(GetNftInfoError::ContractTypeIsNull), }; - let mut nft_metadata = build_nft_from_moralis(*chain, nft_moralis, contract_type, url_antispam).await; + let mut nft_metadata = build_nft_from_moralis(*chain, nft_moralis, contract_type, wrapper.url_antispam).await; protect_from_nft_spam_links(&mut nft_metadata, false)?; Ok(nft_metadata) } @@ -920,14 +956,23 @@ fn check_moralis_ipfs_bafy(token_uri: Option<&str>) -> Option { }) } -async fn get_uri_meta(token_uri: Option<&str>, metadata: Option<&str>, url_antispam: &Url) -> UriMeta { +async fn get_uri_meta( + token_uri: Option<&str>, + metadata: Option<&str>, + url_antispam: &Url, + possible_spam: bool, + possible_phishing: bool, +) -> UriMeta { let mut uri_meta = UriMeta::default(); - // Fetching data from the URL if token_uri is provided - if let Some(token_uri) = token_uri { - if let Some(url) = construct_camo_url_with_token(token_uri, url_antispam) { - uri_meta = fetch_meta_from_url(url).await.unwrap_or_default(); + if !possible_spam && !possible_phishing { + // Fetching data from the URL if token_uri is provided + if let Some(token_uri) = token_uri { + if let Some(url) = construct_camo_url_with_token(token_uri, url_antispam) { + uri_meta = fetch_meta_from_url(url).await.unwrap_or_default(); + } } } + // Filling fields from metadata if provided if let Some(metadata) = metadata { if let Ok(meta_from_meta) = serde_json::from_str::(metadata) { @@ -946,7 +991,7 @@ fn construct_camo_url_with_token(token_uri: &str, url_antispam: &Url) -> Option< } async fn fetch_meta_from_url(url: Url) -> MmResult { - let response_meta = send_request_to_uri(url.as_str()).await?; + let response_meta = send_request_to_uri(url.as_str(), None).await?; serde_json::from_value(response_meta).map_err(|e| e.into()) } @@ -973,11 +1018,10 @@ fn get_transfer_status(my_wallet: &str, to_address: &str) -> TransferStatus { async fn update_nft_list( ctx: MmArc, storage: &T, - chain: &Chain, scan_from_block: u64, - url: &Url, - url_antispam: &Url, + wrapper: &UrlSignWrapper<'_>, ) -> MmResult<(), UpdateNftError> { + let chain = wrapper.chain; let transfers = storage.get_transfers_from_block(*chain, scan_from_block).await?; let req = MyAddressReq { coin: chain.to_ticker().to_string(), @@ -985,27 +1029,26 @@ async fn update_nft_list( }; let my_address = get_my_address(ctx.clone(), req).await?.wallet_address.to_lowercase(); for transfer in transfers.into_iter() { - handle_nft_transfer(storage, chain, url, url_antispam, transfer, &my_address).await?; + handle_nft_transfer(storage, wrapper, transfer, &my_address).await?; } Ok(()) } async fn handle_nft_transfer( storage: &T, - chain: &Chain, - url: &Url, - url_antispam: &Url, + wrapper: &UrlSignWrapper<'_>, transfer: NftTransferHistory, my_address: &str, ) -> MmResult<(), UpdateNftError> { + let chain = wrapper.chain; match (transfer.status, transfer.contract_type) { (TransferStatus::Send, ContractType::Erc721) => handle_send_erc721(storage, chain, transfer).await, (TransferStatus::Receive, ContractType::Erc721) => { - handle_receive_erc721(storage, chain, transfer, url, url_antispam, my_address).await + handle_receive_erc721(storage, transfer, wrapper, my_address).await }, (TransferStatus::Send, ContractType::Erc1155) => handle_send_erc1155(storage, chain, transfer).await, (TransferStatus::Receive, ContractType::Erc1155) => { - handle_receive_erc1155(storage, chain, transfer, url, url_antispam, my_address).await + handle_receive_erc1155(storage, transfer, wrapper, my_address).await }, } } @@ -1039,12 +1082,11 @@ async fn handle_send_erc721 async fn handle_receive_erc721( storage: &T, - chain: &Chain, transfer: NftTransferHistory, - url: &Url, - url_antispam: &Url, + wrapper: &UrlSignWrapper<'_>, my_address: &str, ) -> MmResult<(), UpdateNftError> { + let chain = wrapper.chain; let token_address_str = eth_addr_to_hex(&transfer.common.token_address); match storage .get_nft(chain, token_address_str.clone(), transfer.token_id.clone()) @@ -1065,14 +1107,8 @@ async fn handle_receive_erc721 { - let mut nft = match get_moralis_metadata( - token_address_str.clone(), - transfer.token_id.clone(), - chain, - url, - url_antispam, - ) - .await + let mut nft = match get_moralis_metadata(token_address_str.clone(), transfer.token_id.clone(), wrapper) + .await { Ok(mut moralis_meta) => { // sometimes moralis updates Get All NFTs (which also affects Get Metadata) later @@ -1132,12 +1168,11 @@ async fn handle_send_erc1155( storage: &T, - chain: &Chain, transfer: NftTransferHistory, - url: &Url, - url_antispam: &Url, + wrapper: &UrlSignWrapper<'_>, my_address: &str, ) -> MmResult<(), UpdateNftError> { + let chain = wrapper.chain; let token_address_str = eth_addr_to_hex(&transfer.common.token_address); let mut nft = match storage .get_nft(chain, token_address_str.clone(), transfer.token_id.clone()) @@ -1158,17 +1193,10 @@ async fn handle_receive_erc1155 { - let nft = match get_moralis_metadata( - token_address_str.clone(), - transfer.token_id.clone(), - chain, - url, - url_antispam, - ) - .await - { + let nft = match get_moralis_metadata(token_address_str.clone(), transfer.token_id.clone(), wrapper).await { Ok(moralis_meta) => { - create_nft_from_moralis_metadata(moralis_meta, &transfer, my_address, chain, url_antispam).await? + create_nft_from_moralis_metadata(moralis_meta, &transfer, my_address, chain, wrapper.url_antispam) + .await? }, Err(_) => { mark_as_spam_and_build_empty_meta(storage, chain, token_address_str, &transfer, my_address).await? @@ -1184,8 +1212,18 @@ async fn handle_receive_erc1155) -> MmResult<(), regex::Error> { + if let Some(uri) = token_uri { + if is_malicious(uri)? { + *possible_spam = true; + } + } + Ok(()) +} + async fn create_nft_from_moralis_metadata( - moralis_meta: Nft, + mut moralis_meta: Nft, transfer: &NftTransferHistory, my_address: &str, chain: &Chain, @@ -1193,10 +1231,13 @@ async fn create_nft_from_moralis_metadata( ) -> MmResult { let token_uri = check_moralis_ipfs_bafy(moralis_meta.common.token_uri.as_deref()); let token_domain = get_domain_from_url(token_uri.as_deref()); + check_token_uri(&mut moralis_meta.common.possible_spam, token_uri.as_deref())?; let uri_meta = get_uri_meta( token_uri.as_deref(), moralis_meta.common.metadata.as_deref(), url_antispam, + moralis_meta.common.possible_spam, + moralis_meta.possible_phishing, ) .await; let nft = Nft { @@ -1255,16 +1296,14 @@ async fn mark_as_spam_and_build_empty_meta( ctx: &MmArc, storage: &T, - chain: &Chain, - url: &Url, - url_antispam: &Url, + wrapper: &UrlSignWrapper<'_>, ) -> MmResult, UpdateNftError> { - let nft_list = get_moralis_nft_list(ctx, chain, url, url_antispam).await?; - let last_scanned_block = NftTransferHistoryStorageOps::get_last_block_number(storage, chain) + let nft_list = get_moralis_nft_list(ctx, wrapper).await?; + let last_scanned_block = NftTransferHistoryStorageOps::get_last_block_number(storage, wrapper.chain) .await? .unwrap_or(0); storage - .add_nfts_to_list(*chain, nft_list.clone(), last_scanned_block) + .add_nfts_to_list(*wrapper.chain, nft_list.clone(), last_scanned_block) .await?; Ok(nft_list) } @@ -1281,42 +1320,43 @@ where } /// `update_transfers_with_empty_meta` function updates empty metadata in transfers. -async fn update_transfers_with_empty_meta( - storage: &T, - chain: &Chain, - url: &Url, - url_antispam: &Url, -) -> MmResult<(), UpdateNftError> +async fn update_transfers_with_empty_meta(storage: &T, wrapper: &UrlSignWrapper<'_>) -> MmResult<(), UpdateNftError> where T: NftListStorageOps + NftTransferHistoryStorageOps, { + let chain = wrapper.chain; let token_addr_id = storage.get_transfers_with_empty_meta(*chain).await?; for addr_id_pair in token_addr_id.into_iter() { - let mut nft_meta = match get_moralis_metadata( - addr_id_pair.token_address.clone(), - addr_id_pair.token_id, - chain, - url, - url_antispam, - ) - .await - { - Ok(nft_meta) => nft_meta, - Err(_) => { - storage - .update_nft_spam_by_token_address(chain, addr_id_pair.token_address.clone(), true) - .await?; - storage - .update_transfer_spam_by_token_address(chain, addr_id_pair.token_address, true) - .await?; - continue; - }, - }; + let mut nft_meta = + match get_moralis_metadata(addr_id_pair.token_address.clone(), addr_id_pair.token_id, wrapper).await { + Ok(nft_meta) => nft_meta, + Err(_) => { + storage + .update_nft_spam_by_token_address(chain, addr_id_pair.token_address.clone(), true) + .await?; + storage + .update_transfer_spam_by_token_address(chain, addr_id_pair.token_address, true) + .await?; + continue; + }, + }; update_transfer_meta_using_nft(storage, chain, &mut nft_meta).await?; } Ok(()) } +/// Checks if the given URL is potentially malicious based on certain patterns. +fn is_malicious(token_uri: &str) -> MmResult { + let patterns = vec![r"\.(xyz|gq|top)(/|$)", r"\.(json|xml|jpg|png)[%?]"]; + for pattern in patterns { + let regex = Regex::new(pattern)?; + if regex.is_match(token_uri) { + return Ok(true); + } + } + Ok(false) +} + /// `contains_disallowed_scheme` function checks if the text contains some link. fn contains_disallowed_url(text: &str) -> Result { let url_regex = Regex::new( @@ -1421,15 +1461,20 @@ fn process_metadata_field( async fn build_nft_from_moralis( chain: Chain, - nft_moralis: NftFromMoralis, + mut nft_moralis: NftFromMoralis, contract_type: ContractType, url_antispam: &Url, ) -> Nft { let token_uri = check_moralis_ipfs_bafy(nft_moralis.common.token_uri.as_deref()); + if let Err(e) = check_token_uri(&mut nft_moralis.common.possible_spam, token_uri.as_deref()) { + error!("Error checking token URI: {}", e); + } let uri_meta = get_uri_meta( token_uri.as_deref(), nft_moralis.common.metadata.as_deref(), url_antispam, + nft_moralis.common.possible_spam, + false, ) .await; let token_domain = get_domain_from_url(token_uri.as_deref()); @@ -1513,11 +1558,12 @@ where Ok(()) } -fn construct_moralis_uri_for_nft(base_url: &Url, address: &str, chain: &Chain) -> MmResult { - let mut uri = base_url.clone(); - uri.set_path(MORALIS_API_ENDPOINT); +fn construct_moralis_uri_for_nft(orig_url: &Url, address: &str, chain: &Chain) -> MmResult { + let mut uri = orig_url.clone(); uri.path_segments_mut() .map_to_mm(|_| GetNftInfoError::Internal("Invalid URI".to_string()))? + .push(MORALIS_API) + .push(MORALIS_ENDPOINT_V) .push(address) .push("nft"); uri.query_pairs_mut() @@ -1525,3 +1571,17 @@ fn construct_moralis_uri_for_nft(base_url: &Url, address: &str, chain: &Chain) - .append_pair(MORALIS_FORMAT_QUERY_NAME, MORALIS_FORMAT_QUERY_VALUE); Ok(uri) } + +/// A wrapper struct for holding the chain identifier, original URL field from RPC, anti-spam URL and signed message. +struct UrlSignWrapper<'a> { + chain: &'a Chain, + orig_url: &'a Url, + url_antispam: &'a Url, + proxy_sign: Option, +} + +async fn build_and_send_request(uri: &str, proxy_sign: &Option) -> MmResult { + let payload = proxy_sign.as_ref().map(|msg| serde_json::to_string(&msg)).transpose()?; + let response = send_request_to_uri(uri, payload.as_deref()).await?; + Ok(response) +} diff --git a/mm2src/coins/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs index 5e35b138ff..12e8d326a0 100644 --- a/mm2src/coins/nft/nft_errors.rs +++ b/mm2src/coins/nft/nft_errors.rs @@ -1,8 +1,10 @@ +use crate::eth::v2_activation::GenerateSignedMessageError; use crate::eth::GetEthAddressError; #[cfg(target_arch = "wasm32")] use crate::nft::storage::wasm::WasmNftCacheError; use crate::nft::storage::NftStorageError; -use crate::{CoinFindError, GetMyAddressError, NumConversError, UnexpectedDerivationMethod, WithdrawError}; +use crate::{CoinFindError, GetMyAddressError, MyAddressError, NumConversError, PrivKeyPolicyNotAllowed, + UnexpectedDerivationMethod, WithdrawError}; use common::{HttpStatusCode, ParseRfc3339Err}; #[cfg(not(target_arch = "wasm32"))] use db_common::sqlite::rusqlite::Error as SqlError; @@ -155,6 +157,7 @@ pub enum UpdateNftError { #[from_stringify("LockDBError")] #[display(fmt = "DB error {}", _0)] DbError(String), + #[from_stringify("regex::Error", "MyAddressError")] #[display(fmt = "Internal: {}", _0)] Internal(String), GetNftInfoError(GetNftInfoError), @@ -212,6 +215,8 @@ pub enum UpdateNftError { CoinDoesntSupportNft { coin: String, }, + #[display(fmt = "Private key policy is not allowed: {}", _0)] + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), } impl From for UpdateNftError { @@ -246,6 +251,19 @@ impl From for UpdateNftError { } } +impl From for UpdateNftError { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { Self::PrivKeyPolicyNotAllowed(e) } +} + +impl From for UpdateNftError { + fn from(e: GenerateSignedMessageError) -> Self { + match e { + GenerateSignedMessageError::InternalError(e) => UpdateNftError::Internal(e), + GenerateSignedMessageError::PrivKeyPolicyNotAllowed(e) => UpdateNftError::PrivKeyPolicyNotAllowed(e), + } + } +} + impl HttpStatusCode for UpdateNftError { fn status_code(&self) -> StatusCode { match self { @@ -264,7 +282,8 @@ impl HttpStatusCode for UpdateNftError { | UpdateNftError::SerdeError(_) | UpdateNftError::ProtectFromSpamError(_) | UpdateNftError::NoSuchCoin { .. } - | UpdateNftError::CoinDoesntSupportNft { .. } => StatusCode::INTERNAL_SERVER_ERROR, + | UpdateNftError::CoinDoesntSupportNft { .. } + | UpdateNftError::PrivKeyPolicyNotAllowed(_) => StatusCode::INTERNAL_SERVER_ERROR, } } } diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index 0096e8f2fe..e1412933d4 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -21,6 +21,9 @@ use crate::nft::nft_errors::{LockDBError, ParseChainTypeError, ParseContractType use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps}; use crate::{TransactionType, TxFeeDetails, WithdrawFee}; +#[cfg(not(target_arch = "wasm32"))] +use crate::nft::storage::NftMigrationOps; + cfg_native! { use db_common::async_sql_conn::AsyncConnection; use futures::lock::Mutex as AsyncMutex; @@ -98,6 +101,8 @@ pub struct RefreshMetadataReq { /// URL used to validate if the fetched contract addresses are associated /// with spam contracts or if domain fields in the fetched metadata match known phishing domains. pub(crate) url_antispam: Url, + #[serde(default)] + pub(crate) komodo_proxy: bool, } /// Represents blockchains which are supported by NFT feature. @@ -436,7 +441,8 @@ pub struct WithdrawErc1155 { #[serde(deserialize_with = "deserialize_token_id")] pub(crate) token_id: BigUint, /// Optional amount of the token to withdraw. Defaults to 1 if not specified. - pub(crate) amount: Option, + #[serde(deserialize_with = "deserialize_opt_biguint")] + pub(crate) amount: Option, /// If set to `true`, withdraws the maximum amount available. Overrides the `amount` field. #[serde(default)] pub(crate) max: bool, @@ -487,7 +493,7 @@ pub struct TransactionNftDetails { pub(crate) token_address: String, #[serde(serialize_with = "serialize_token_id")] pub(crate) token_id: BigUint, - pub(crate) amount: BigDecimal, + pub(crate) amount: BigUint, pub(crate) fee_details: Option, /// The coin transaction belongs to pub(crate) coin: String, @@ -660,6 +666,8 @@ pub struct UpdateNftReq { /// URL used to validate if the fetched contract addresses are associated /// with spam contracts or if domain fields in the fetched metadata match known phishing domains. pub(crate) url_antispam: Url, + #[serde(default)] + pub(crate) komodo_proxy: bool, } /// Represents a unique identifier for an NFT, consisting of its token address and token ID. @@ -726,14 +734,15 @@ impl NftCtx { /// If an `NftCtx` instance doesn't already exist in the MM context, it gets created and cached for subsequent use. #[cfg(not(target_arch = "wasm32"))] pub(crate) fn from_ctx(ctx: &MmArc) -> Result, String> { - Ok(try_s!(from_ctx(&ctx.nft_ctx, move || { + from_ctx(&ctx.nft_ctx, move || { let async_sqlite_connection = ctx .async_sqlite_connection + .get() .ok_or("async_sqlite_connection is not initialized".to_owned())?; Ok(NftCtx { nft_cache_db: async_sqlite_connection.clone(), }) - }))) + }) } #[cfg(target_arch = "wasm32")] @@ -749,7 +758,7 @@ impl NftCtx { #[cfg(not(target_arch = "wasm32"))] pub(crate) async fn lock_db( &self, - ) -> MmResult { + ) -> MmResult { Ok(self.nft_cache_db.lock().await) } @@ -802,10 +811,24 @@ where BigUint::from_str(&s).map_err(serde::de::Error::custom) } +/// Custom deserialization function for optional BigUint. +fn deserialize_opt_biguint<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let opt: Option = Option::deserialize(deserializer)?; + if let Some(s) = opt { + BigUint::from_str(&s).map(Some).map_err(serde::de::Error::custom) + } else { + Ok(None) + } +} + /// Request parameters for clearing NFT data from the database. #[derive(Debug, Deserialize)] pub struct ClearNftDbReq { /// Specifies the blockchain networks (e.g., Ethereum, BSC) to clear NFT data. + #[serde(default)] pub(crate) chains: Vec, /// If `true`, clears NFT data for all chains, ignoring the `chains` field. Defaults to `false`. #[serde(default)] diff --git a/mm2src/coins/nft/nft_tests.rs b/mm2src/coins/nft/nft_tests.rs index f0dd57603c..71001d8f21 100644 --- a/mm2src/coins/nft/nft_tests.rs +++ b/mm2src/coins/nft/nft_tests.rs @@ -4,7 +4,7 @@ use crate::nft::nft_structs::{Chain, NftFromMoralis, NftListFilters, NftTransfer SpamContractRes, TransferMeta, UriMeta}; use crate::nft::storage::db_test_helpers::{get_nft_ctx, nft, nft_list, nft_transfer_history}; use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps, RemoveNftResult}; -use crate::nft::{check_moralis_ipfs_bafy, get_domain_from_url, process_metadata_for_spam_link, +use crate::nft::{check_moralis_ipfs_bafy, get_domain_from_url, is_malicious, process_metadata_for_spam_link, process_text_for_spam_link}; use common::cross_test; use ethereum_types::Address; @@ -30,6 +30,14 @@ common::cfg_wasm32! { use mm2_net::wasm::http::send_request_to_uri; } +cross_test!(test_is_malicious, { + let token_uri = "https://btrgtrhbyjuyj.xyz/BABYDOGE.json"; + assert!(is_malicious(token_uri).unwrap()); + + let token_uri1 = "https://btrgtrhbyjuyj.com/BABYDOGE.json%00"; + assert!(is_malicious(token_uri1).unwrap()); +}); + cross_test!(test_moralis_ipfs_bafy, { let uri = "https://ipfs.moralis.io:2053/ipfs/bafybeifnek24coy5xj5qabdwh24dlp5omq34nzgvazkfyxgnqms4eidsiq/1.json"; let res_uri = check_moralis_ipfs_bafy(Some(uri)); @@ -86,7 +94,7 @@ cross_test!(test_moralis_requests, { "{}/{}/nft?chain=POLYGON&format=decimal", MORALIS_API_ENDPOINT_TEST, TEST_WALLET_ADDR_EVM ); - let response_nft_list = send_request_to_uri(uri_nft_list.as_str()).await.unwrap(); + let response_nft_list = send_request_to_uri(uri_nft_list.as_str(), None).await.unwrap(); let nfts_list = response_nft_list["result"].as_array().unwrap(); for nft_json in nfts_list { let nft_moralis: NftFromMoralis = serde_json::from_str(&nft_json.to_string()).unwrap(); @@ -97,7 +105,7 @@ cross_test!(test_moralis_requests, { "{}/{}/nft/transfers?chain=POLYGON&format=decimal", MORALIS_API_ENDPOINT_TEST, TEST_WALLET_ADDR_EVM ); - let response_transfer_history = send_request_to_uri(uri_history.as_str()).await.unwrap(); + let response_transfer_history = send_request_to_uri(uri_history.as_str(), None).await.unwrap(); let mut transfer_list = response_transfer_history["result"].as_array().unwrap().clone(); assert!(!transfer_list.is_empty()); let first_transfer = transfer_list.remove(transfer_list.len() - 1); @@ -111,7 +119,7 @@ cross_test!(test_moralis_requests, { "{}/nft/0xed55e4477b795eaa9bb4bca24df42214e1a05c18/1111777?chain=POLYGON&format=decimal", MORALIS_API_ENDPOINT_TEST ); - let response_meta = send_request_to_uri(uri_meta.as_str()).await.unwrap(); + let response_meta = send_request_to_uri(uri_meta.as_str(), None).await.unwrap(); let nft_moralis: NftFromMoralis = serde_json::from_str(&response_meta.to_string()).unwrap(); assert_eq!(42563567, nft_moralis.block_number.0); }); @@ -147,7 +155,7 @@ cross_test!(test_antispam_scan_endpoints, { cross_test!(test_camo, { let hex_token_uri = hex::encode("https://tikimetadata.s3.amazonaws.com/tiki_box.json"); let uri_decode = format!("{}/url/decode/{}", BLOCKLIST_API_ENDPOINT, hex_token_uri); - let decode_res = send_request_to_uri(&uri_decode).await.unwrap(); + let decode_res = send_request_to_uri(&uri_decode, None).await.unwrap(); let uri_meta: UriMeta = serde_json::from_value(decode_res).unwrap(); assert_eq!( uri_meta.raw_image_url.unwrap(), @@ -455,7 +463,12 @@ cross_test!(test_add_get_transfers, { .clone(); assert_eq!(transfer1.block_number, 28056721); let transfer2 = storage - .get_transfer_by_tx_hash_and_log_index(&chain, TX_HASH.to_string(), LOG_INDEX) + .get_transfer_by_tx_hash_log_index_token_id( + &chain, + TX_HASH.to_string(), + LOG_INDEX, + BigUint::from_str("214300047253").unwrap(), + ) .await .unwrap() .unwrap(); diff --git a/mm2src/coins/nft/storage/db_test_helpers.rs b/mm2src/coins/nft/storage/db_test_helpers.rs index d59b845661..2565be8f2e 100644 --- a/mm2src/coins/nft/storage/db_test_helpers.rs +++ b/mm2src/coins/nft/storage/db_test_helpers.rs @@ -258,7 +258,7 @@ pub(crate) fn nft_transfer_history() -> Vec { transaction_index: Some(198), log_index: 495, value: Default::default(), - transaction_type: Some("Single".to_string()), + transaction_type: Some("Batch".to_string()), token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(), to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), @@ -284,15 +284,15 @@ pub(crate) fn nft_transfer_history() -> Vec { confirmations: 0, }; - // Same as transfer1 but with different log_index, meaning that transfer1 and transfer2 are part of one batch/multi token transaction + // Same as transfer1 (identical tx hash and log index) but with different token_id, meaning that transfer1 and transfer2 are part of one batch/multi token transaction let transfer2 = NftTransferHistory { common: NftTransferCommon { block_hash: Some("0x3d68b78391fb3cf8570df27036214f7e9a5a6a45d309197936f51d826041bfe7".to_string()), transaction_hash: "0x1e9f04e9b571b283bde02c98c2a97da39b2bb665b57c1f2b0b733f9b681debbe".to_string(), transaction_index: Some(198), - log_index: 496, + log_index: 495, value: Default::default(), - transaction_type: Some("Single".to_string()), + transaction_type: Some("Batch".to_string()), token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(), to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), diff --git a/mm2src/coins/nft/storage/mod.rs b/mm2src/coins/nft/storage/mod.rs index ad255100c3..3eed941b5d 100644 --- a/mm2src/coins/nft/storage/mod.rs +++ b/mm2src/coins/nft/storage/mod.rs @@ -156,11 +156,12 @@ pub trait NftTransferHistoryStorageOps { token_id: BigUint, ) -> MmResult, Self::Error>; - async fn get_transfer_by_tx_hash_and_log_index( + async fn get_transfer_by_tx_hash_log_index_token_id( &self, chain: &Chain, transaction_hash: String, log_index: u32, + token_id: BigUint, ) -> MmResult, Self::Error>; /// Updates the metadata for NFT transfers identified by their token address and ID. @@ -243,3 +244,10 @@ pub(crate) struct TransferDetailsJson { pub(crate) to_address: Address, pub(crate) fee_details: Option, } + +#[async_trait] +pub trait NftMigrationOps { + type Error: NftStorageError; + + async fn migrate_tx_history_if_needed(&self, chain: &Chain) -> MmResult<(), Self::Error>; +} diff --git a/mm2src/coins/nft/storage/sql_storage.rs b/mm2src/coins/nft/storage/sql_storage.rs index 6844b261d9..bffdaef27b 100644 --- a/mm2src/coins/nft/storage/sql_storage.rs +++ b/mm2src/coins/nft/storage/sql_storage.rs @@ -2,10 +2,10 @@ use crate::nft::eth_addr_to_hex; use crate::nft::nft_structs::{Chain, ContractType, ConvertChain, Nft, NftCommon, NftList, NftListFilters, NftTokenAddrId, NftTransferCommon, NftTransferHistory, NftTransferHistoryFilters, NftsTransferHistoryList, TransferMeta, UriMeta}; -use crate::nft::storage::{get_offset_limit, NftDetailsJson, NftListStorageOps, NftStorageError, +use crate::nft::storage::{get_offset_limit, NftDetailsJson, NftListStorageOps, NftMigrationOps, NftStorageError, NftTransferHistoryStorageOps, RemoveNftResult, TransferDetailsJson}; use async_trait::async_trait; -use db_common::async_sql_conn::{AsyncConnError, AsyncConnection}; +use db_common::async_sql_conn::{AsyncConnError, AsyncConnection, InternalError}; use db_common::sql_build::{SqlCondition, SqlQuery}; use db_common::sqlite::rusqlite::types::{FromSqlError, Type}; use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Result as SqlResult, Row, Statement}; @@ -22,6 +22,8 @@ use std::convert::TryInto; use std::num::NonZeroUsize; use std::str::FromStr; +const CURRENT_SCHEMA_VERSION_TX_HISTORY: i32 = 2; + impl Chain { fn nft_list_table_name(&self) -> SqlResult { let name = self.to_ticker().to_owned() + "_nft_list"; @@ -42,6 +44,12 @@ fn scanned_nft_blocks_table_name() -> SqlResult { Ok(safe_name) } +fn schema_versions_table_name() -> SqlResult { + let name = "schema_versions".to_string(); + let safe_name = SafeTableName::new(&name)?; + Ok(safe_name) +} + fn create_nft_list_table_sql(chain: &Chain) -> MmResult { let safe_table_name = chain.nft_list_table_name()?; let sql = format!( @@ -82,6 +90,11 @@ fn create_nft_list_table_sql(chain: &Chain) -> MmResult { fn create_transfer_history_table_sql(chain: &Chain) -> Result { let safe_table_name = chain.transfer_history_table_name()?; + create_transfer_history_table_sql_custom_name(&safe_table_name) +} + +/// Supports [CURRENT_SCHEMA_VERSION_TX_HISTORY] +fn create_transfer_history_table_sql_custom_name(safe_table_name: &SafeTableName) -> Result { let sql = format!( "CREATE TABLE IF NOT EXISTS {} ( transaction_hash VARCHAR(256) NOT NULL, @@ -103,7 +116,7 @@ fn create_transfer_history_table_sql(chain: &Chain) -> Result image_domain TEXT, token_name TEXT, details_json TEXT, - PRIMARY KEY (transaction_hash, log_index) + PRIMARY KEY (transaction_hash, log_index, token_id) );", safe_table_name.inner() ); @@ -122,6 +135,18 @@ fn create_scanned_nft_blocks_sql() -> Result { Ok(sql) } +fn create_schema_versions_sql() -> Result { + let safe_table_name = schema_versions_table_name()?; + let sql = format!( + "CREATE TABLE IF NOT EXISTS {} ( + table_name TEXT PRIMARY KEY, + version INTEGER NOT NULL + );", + safe_table_name.inner() + ); + Ok(sql) +} + impl NftStorageError for AsyncConnError {} fn get_nft_list_builder_preimage(chains: Vec, filters: Option) -> Result { @@ -432,6 +457,15 @@ fn upsert_last_scanned_block_sql() -> Result { Ok(sql) } +fn insert_schema_version_sql() -> Result { + let schema_table = schema_versions_table_name()?; + let sql = format!( + "INSERT INTO {} (table_name, version) VALUES (?1, ?2) ON CONFLICT(table_name) DO NOTHING;", + schema_table.inner() + ); + Ok(sql) +} + fn refresh_nft_metadata_sql(chain: &Chain) -> Result { let safe_table_name = chain.nft_list_table_name()?; let sql = format!( @@ -462,12 +496,25 @@ fn update_transfer_spam_by_token_addr_id(chain: &Chain) -> Result Result { - let sql = format!( +/// Generates the SQL command to insert or update the schema version in the `schema_versions` table. +/// +/// This function creates an SQL command that attempts to insert a new row with the specified +/// `table_name` and `version`. If a row with the same `table_name` already exists, the `version` +/// field is updated to the new value provided. +fn update_schema_version_sql(schema_versions: &SafeTableName) -> String { + format!( + "INSERT INTO {} (table_name, version) + VALUES (?1, ?2) + ON CONFLICT(table_name) DO UPDATE SET version = excluded.version;", + schema_versions.inner() + ) +} + +fn select_last_block_number_sql(safe_table_name: SafeTableName) -> String { + format!( "SELECT block_number FROM {} ORDER BY block_number DESC LIMIT 1", safe_table_name.inner() - ); - Ok(sql) + ) } fn select_last_scanned_block_sql() -> MmResult { @@ -540,6 +587,13 @@ fn get_transfers_with_empty_meta_builder<'a>(conn: &'a Connection, chain: &'a Ch Ok(sql_builder) } +fn get_schema_version_stmt(conn: &Connection) -> Result { + let table_name = schema_versions_table_name()?; + let sql = format!("SELECT version FROM {} WHERE table_name = ?1;", table_name.inner()); + let stmt = conn.prepare(&sql)?; + Ok(stmt) +} + fn is_table_empty(conn: &Connection, safe_table_name: SafeTableName) -> Result { let query = format!("SELECT COUNT(*) FROM {}", safe_table_name.inner()); conn.query_row(&query, [], |row| row.get::<_, i64>(0)) @@ -777,7 +831,7 @@ impl NftListStorageOps for AsyncMutexGuard<'_, AsyncConnection> { async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error> { let table_name = chain.nft_list_table_name()?; - let sql = select_last_block_number_sql(table_name)?; + let sql = select_last_block_number_sql(table_name); self.call(move |conn| { let block_number = query_single_row(conn, &sql, [], block_number_from_row)?; Ok(block_number) @@ -969,8 +1023,15 @@ impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { async fn init(&self, chain: &Chain) -> MmResult<(), Self::Error> { let sql_transfer_history = create_transfer_history_table_sql(chain)?; + let table_name = chain.transfer_history_table_name()?; self.call(move |conn| { conn.execute(&sql_transfer_history, []).map(|_| ())?; + conn.execute(&create_schema_versions_sql()?, []).map(|_| ())?; + conn.execute(&insert_schema_version_sql()?, [ + table_name.inner(), + &CURRENT_SCHEMA_VERSION_TX_HISTORY.to_string(), + ]) + .map(|_| ())?; Ok(()) }) .await @@ -978,11 +1039,10 @@ impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { } async fn is_initialized(&self, chain: &Chain) -> MmResult { - let table_name = chain.transfer_history_table_name()?; + let table = chain.transfer_history_table_name()?; self.call(move |conn| { - let nft_list_initialized = - query_single_row(conn, CHECK_TABLE_EXISTS_SQL, [table_name.inner()], string_from_row)?; - Ok(nft_list_initialized.is_some()) + let table_exists = query_single_row(conn, CHECK_TABLE_EXISTS_SQL, [table.inner()], string_from_row)?; + Ok(table_exists.is_some()) }) .await .map_to_mm(AsyncConnError::from) @@ -1077,7 +1137,7 @@ impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error> { let table_name = chain.transfer_history_table_name()?; - let sql = select_last_block_number_sql(table_name)?; + let sql = select_last_block_number_sql(table_name); self.call(move |conn| { let block_number = query_single_row(conn, &sql, [], block_number_from_row)?; Ok(block_number) @@ -1121,22 +1181,23 @@ impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { .map_to_mm(AsyncConnError::from) } - async fn get_transfer_by_tx_hash_and_log_index( + async fn get_transfer_by_tx_hash_log_index_token_id( &self, chain: &Chain, transaction_hash: String, log_index: u32, + token_id: BigUint, ) -> MmResult, Self::Error> { let table_name = chain.transfer_history_table_name()?; let sql = format!( - "SELECT * FROM {} WHERE transaction_hash=?1 AND log_index = ?2", + "SELECT * FROM {} WHERE transaction_hash=?1 AND log_index = ?2 AND token_id = ?3", table_name.inner() ); self.call(move |conn| { let transfer = query_single_row( conn, &sql, - [transaction_hash, log_index.to_string()], + [transaction_hash, log_index.to_string(), token_id.to_string()], transfer_history_from_row, )?; Ok(transfer) @@ -1285,11 +1346,18 @@ impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { } async fn clear_history_data(&self, chain: &Chain) -> MmResult<(), Self::Error> { - let table_name = chain.transfer_history_table_name()?; + let history_table_name = chain.transfer_history_table_name()?; + let schema_table_name = schema_versions_table_name()?; + let dlt_schema_sql = format!("DELETE from {} where table_name=?1", schema_table_name.inner()); self.call(move |conn| { let sql_transaction = conn.transaction()?; - sql_transaction.execute(&format!("DROP TABLE IF EXISTS {};", table_name.inner()), [])?; + sql_transaction.execute(&format!("DROP TABLE IF EXISTS {};", history_table_name.inner()), [])?; + sql_transaction.execute(&dlt_schema_sql, [history_table_name.inner()])?; sql_transaction.commit()?; + if is_table_empty(conn, schema_table_name.clone())? { + conn.execute(&format!("DROP TABLE IF EXISTS {};", schema_table_name.inner()), []) + .map(|_| ())?; + } Ok(()) }) .await @@ -1297,12 +1365,14 @@ impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { } async fn clear_all_history_data(&self) -> MmResult<(), Self::Error> { + let schema_table = schema_versions_table_name()?; self.call(move |conn| { let sql_transaction = conn.transaction()?; for chain in Chain::variant_list().into_iter() { let table_name = chain.transfer_history_table_name()?; sql_transaction.execute(&format!("DROP TABLE IF EXISTS {};", table_name.inner()), [])?; } + sql_transaction.execute(&format!("DROP TABLE IF EXISTS {};", schema_table.inner()), [])?; sql_transaction.commit()?; Ok(()) }) @@ -1310,3 +1380,112 @@ impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { .map_to_mm(AsyncConnError::from) } } + +fn migrate_tx_history_table_from_schema_0_to_2( + conn: &mut Connection, + history_table: &SafeTableName, + schema_table: &SafeTableName, +) -> Result<(), AsyncConnError> { + if has_primary_key_duplication(conn, history_table)? { + return Err(AsyncConnError::Internal(InternalError( + "Primary key duplication occurred in old nft tx history table".to_string(), + ))); + } + + // Start a transaction to ensure all operations are atomic + let sql_tx = conn.transaction()?; + + // Create the temporary table with the new schema + let temp_table_name = SafeTableName::new(format!("{}_temp", history_table.inner()).as_str())?; + sql_tx.execute(&create_transfer_history_table_sql_custom_name(&temp_table_name)?, [])?; + + // I don't think we need to batch the data copy process here. + // It's unlikely that the table will grow to 1 million+ rows (as an example). + let copy_data_sql = format!( + "INSERT INTO {} SELECT * FROM {};", + temp_table_name.inner(), + history_table.inner() + ); + sql_tx.execute(©_data_sql, [])?; + + let drop_old_table_sql = format!("DROP TABLE IF EXISTS {};", history_table.inner()); + sql_tx.execute(&drop_old_table_sql, [])?; + + let rename_table_sql = format!( + "ALTER TABLE {} RENAME TO {};", + temp_table_name.inner(), + history_table.inner() + ); + sql_tx.execute(&rename_table_sql, [])?; + + sql_tx.execute(&update_schema_version_sql(schema_table), [ + history_table.inner().to_string(), + CURRENT_SCHEMA_VERSION_TX_HISTORY.to_string(), + ])?; + + sql_tx.commit()?; + + Ok(()) +} + +/// Query to check for duplicates based on the primary key columns from tx history table version 2 +fn has_primary_key_duplication(conn: &Connection, safe_table_name: &SafeTableName) -> Result { + let query = format!( + "SELECT EXISTS ( + SELECT 1 + FROM {} + GROUP BY transaction_hash, log_index, token_id + HAVING COUNT(*) > 1 + );", + safe_table_name.inner() + ); + // return true if duplicates exist, false otherwise + conn.query_row(&query, [], |row| row.get::<_, i32>(0)) + .map(|exists| exists == 1) +} + +#[async_trait] +impl NftMigrationOps for AsyncMutexGuard<'_, AsyncConnection> { + type Error = AsyncConnError; + + async fn migrate_tx_history_if_needed(&self, chain: &Chain) -> MmResult<(), Self::Error> { + let history_table = chain.transfer_history_table_name()?; + let schema_table = schema_versions_table_name()?; + self.call(move |conn| { + let schema_table_exists = + query_single_row(conn, CHECK_TABLE_EXISTS_SQL, [schema_table.inner()], string_from_row)?; + + let mut version = if schema_table_exists.is_some() { + get_schema_version_stmt(conn)? + .query_row([history_table.inner()], |row| row.get(0)) + .unwrap_or(0) + } else { + conn.execute(&create_schema_versions_sql()?, []).map(|_| ())?; + 0 + }; + + while version < CURRENT_SCHEMA_VERSION_TX_HISTORY { + match version { + 0 => { + migrate_tx_history_table_from_schema_0_to_2(conn, &history_table, &schema_table)?; + }, + 1 => { + // The Tx History SQL schema didn't have version 1, but let's handle this case + // for consistency with IndexedDB versioning, where the current Tx History schema is at version 2. + }, + unsupported_version => { + return Err(AsyncConnError::Internal(InternalError(format!( + "Unsupported schema version {}", + unsupported_version + )))); + }, + } + version += 1; + } + + Ok(()) + }) + .await + .map_to_mm(AsyncConnError::from) + } +} diff --git a/mm2src/coins/nft/storage/wasm/nft_idb.rs b/mm2src/coins/nft/storage/wasm/nft_idb.rs index 054f1c058e..775a871589 100644 --- a/mm2src/coins/nft/storage/wasm/nft_idb.rs +++ b/mm2src/coins/nft/storage/wasm/nft_idb.rs @@ -3,7 +3,8 @@ use async_trait::async_trait; use mm2_db::indexed_db::InitDbResult; use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbLocked, IndexedDb, IndexedDbBuilder}; -const DB_VERSION: u32 = 1; +/// prim key was changed in NftTransferHistoryTable, schemas of the other tables remain the same. +const DB_VERSION: u32 = 2; /// Represents a locked instance of the `NftCacheIDB` database. /// diff --git a/mm2src/coins/nft/storage/wasm/wasm_storage.rs b/mm2src/coins/nft/storage/wasm/wasm_storage.rs index e5ea955918..99e76ba04f 100644 --- a/mm2src/coins/nft/storage/wasm/wasm_storage.rs +++ b/mm2src/coins/nft/storage/wasm/wasm_storage.rs @@ -6,11 +6,10 @@ use crate::nft::storage::wasm::{WasmNftCacheError, WasmNftCacheResult}; use crate::nft::storage::{get_offset_limit, NftListStorageOps, NftTokenAddrId, NftTransferHistoryFilters, NftTransferHistoryStorageOps, RemoveNftResult}; use async_trait::async_trait; -use common::is_initial_upgrade; use ethereum_types::Address; -use mm2_db::indexed_db::{BeBigUint, DbTable, DbUpgrader, MultiIndex, OnUpgradeResult, TableSignature}; +use mm2_db::indexed_db::{BeBigUint, DbTable, DbUpgrader, MultiIndex, OnUpgradeError, OnUpgradeResult, TableSignature}; use mm2_err_handle::map_to_mm::MapToMmResult; -use mm2_err_handle::prelude::MmResult; +use mm2_err_handle::prelude::{MmError, MmResult}; use mm2_number::BigUint; use num_traits::ToPrimitive; use serde_json::{self as json, Value as Json}; @@ -547,18 +546,20 @@ impl NftTransferHistoryStorageOps for NftCacheIDBLocked<'_> { .collect() } - async fn get_transfer_by_tx_hash_and_log_index( + async fn get_transfer_by_tx_hash_log_index_token_id( &self, chain: &Chain, transaction_hash: String, log_index: u32, + token_id: BigUint, ) -> MmResult, Self::Error> { let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(&transaction_hash)? - .with_value(log_index)?; + .with_value(log_index)? + .with_value(BeBigUint::from(token_id))?; if let Some((_item_id, item)) = table.get_item_by_unique_multi_index(index_keys).await? { Ok(Some(transfer_details_from_item(item)?)) @@ -602,10 +603,11 @@ impl NftTransferHistoryStorageOps for NftCacheIDBLocked<'_> { } drop_mutability!(transfer); - let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_TOKEN_ID_INDEX) .with_value(&chain_str)? .with_value(&transfer.common.transaction_hash)? - .with_value(transfer.common.log_index)?; + .with_value(transfer.common.log_index)? + .with_value(BeBigUint::from(transfer.token_id.clone()))?; let item = NftTransferHistoryTable::from_transfer_history(&transfer)?; table.replace_item_by_unique_multi_index(index_keys, &item).await?; @@ -691,10 +693,11 @@ impl NftTransferHistoryStorageOps for NftCacheIDBLocked<'_> { transfer.common.possible_spam = possible_spam; drop_mutability!(transfer); - let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_TOKEN_ID_INDEX) .with_value(&chain_str)? .with_value(&transfer.common.transaction_hash)? - .with_value(transfer.common.log_index)?; + .with_value(transfer.common.log_index)? + .with_value(BeBigUint::from(transfer.token_id.clone()))?; let item = NftTransferHistoryTable::from_transfer_history(&transfer)?; table.replace_item_by_unique_multi_index(index_keys, &item).await?; @@ -777,10 +780,11 @@ async fn update_transfer_phishing_for_index( transfer.possible_phishing = possible_phishing; drop_mutability!(transfer); let transfer_item = NftTransferHistoryTable::from_transfer_history(&transfer)?; - let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_TOKEN_ID_INDEX) .with_value(chain)? .with_value(&transfer.common.transaction_hash)? - .with_value(transfer.common.log_index)?; + .with_value(transfer.common.log_index)? + .with_value(BeBigUint::from(transfer.token_id))?; table .replace_item_by_unique_multi_index(index_keys, &transfer_item) .await?; @@ -876,8 +880,8 @@ pub(crate) struct NftListTable { } impl NftListTable { - const CHAIN_ANIMATION_DOMAIN_INDEX: &str = "chain_animation_domain_index"; - const CHAIN_EXTERNAL_DOMAIN_INDEX: &str = "chain_external_domain_index"; + const CHAIN_ANIMATION_DOMAIN_INDEX: &'static str = "chain_animation_domain_index"; + const CHAIN_EXTERNAL_DOMAIN_INDEX: &'static str = "chain_external_domain_index"; fn from_nft(nft: &Nft) -> WasmNftCacheResult { let details_json = json::to_value(nft).map_to_mm(|e| WasmNftCacheError::ErrorSerializing(e.to_string()))?; @@ -902,26 +906,45 @@ impl NftListTable { impl TableSignature for NftListTable { const TABLE_NAME: &'static str = "nft_list_cache_table"; - fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - if is_initial_upgrade(old_version, new_version) { - let table = upgrader.create_table(Self::TABLE_NAME)?; - table.create_multi_index( - CHAIN_TOKEN_ADD_TOKEN_ID_INDEX, - &["chain", "token_address", "token_id"], - true, - )?; - table.create_multi_index(CHAIN_BLOCK_NUMBER_INDEX, &["chain", "block_number"], false)?; - table.create_multi_index(CHAIN_TOKEN_ADD_INDEX, &["chain", "token_address"], false)?; - table.create_multi_index(CHAIN_TOKEN_DOMAIN_INDEX, &["chain", "token_domain"], false)?; - table.create_multi_index(CHAIN_IMAGE_DOMAIN_INDEX, &["chain", "image_domain"], false)?; - table.create_multi_index( - Self::CHAIN_ANIMATION_DOMAIN_INDEX, - &["chain", "animation_domain"], - false, - )?; - table.create_multi_index(Self::CHAIN_EXTERNAL_DOMAIN_INDEX, &["chain", "external_domain"], false)?; - table.create_index("chain", false)?; - table.create_index("block_number", false)?; + fn on_upgrade_needed(upgrader: &DbUpgrader, mut old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + while old_version < new_version { + match old_version { + 0 => { + let table = upgrader.create_table(Self::TABLE_NAME)?; + table.create_multi_index( + CHAIN_TOKEN_ADD_TOKEN_ID_INDEX, + &["chain", "token_address", "token_id"], + true, + )?; + table.create_multi_index(CHAIN_BLOCK_NUMBER_INDEX, &["chain", "block_number"], false)?; + table.create_multi_index(CHAIN_TOKEN_ADD_INDEX, &["chain", "token_address"], false)?; + table.create_multi_index(CHAIN_TOKEN_DOMAIN_INDEX, &["chain", "token_domain"], false)?; + table.create_multi_index(CHAIN_IMAGE_DOMAIN_INDEX, &["chain", "image_domain"], false)?; + table.create_multi_index( + Self::CHAIN_ANIMATION_DOMAIN_INDEX, + &["chain", "animation_domain"], + false, + )?; + table.create_multi_index( + Self::CHAIN_EXTERNAL_DOMAIN_INDEX, + &["chain", "external_domain"], + false, + )?; + table.create_index("chain", false)?; + table.create_index("block_number", false)?; + }, + 1 => { + // nothing to change + }, + unsupported_version => { + return MmError::err(OnUpgradeError::UnsupportedVersion { + unsupported_version, + old_version, + new_version, + }) + }, + } + old_version += 1; } Ok(()) } @@ -951,7 +974,10 @@ pub(crate) struct NftTransferHistoryTable { } impl NftTransferHistoryTable { - const CHAIN_TX_HASH_LOG_INDEX_INDEX: &str = "chain_tx_hash_log_index_index"; + // old prim key index for DB_VERSION = 1 + const CHAIN_TX_HASH_LOG_INDEX_INDEX: &'static str = "chain_tx_hash_log_index_index"; + // prim key multi index for DB_VERSION = 2 + const CHAIN_TX_HASH_LOG_INDEX_TOKEN_ID_INDEX: &'static str = "chain_tx_hash_log_index_token_id_index"; fn from_transfer_history(transfer: &NftTransferHistory) -> WasmNftCacheResult { let details_json = @@ -983,25 +1009,47 @@ impl NftTransferHistoryTable { impl TableSignature for NftTransferHistoryTable { const TABLE_NAME: &'static str = "nft_transfer_history_cache_table"; - fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - if is_initial_upgrade(old_version, new_version) { - let table = upgrader.create_table(Self::TABLE_NAME)?; - table.create_multi_index( - CHAIN_TOKEN_ADD_TOKEN_ID_INDEX, - &["chain", "token_address", "token_id"], - false, - )?; - table.create_multi_index( - Self::CHAIN_TX_HASH_LOG_INDEX_INDEX, - &["chain", "transaction_hash", "log_index"], - true, - )?; - table.create_multi_index(CHAIN_BLOCK_NUMBER_INDEX, &["chain", "block_number"], false)?; - table.create_multi_index(CHAIN_TOKEN_ADD_INDEX, &["chain", "token_address"], false)?; - table.create_multi_index(CHAIN_TOKEN_DOMAIN_INDEX, &["chain", "token_domain"], false)?; - table.create_multi_index(CHAIN_IMAGE_DOMAIN_INDEX, &["chain", "image_domain"], false)?; - table.create_index("block_number", false)?; - table.create_index("chain", false)?; + fn on_upgrade_needed(upgrader: &DbUpgrader, mut old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + while old_version < new_version { + match old_version { + 0 => { + let table = upgrader.create_table(Self::TABLE_NAME)?; + table.create_multi_index( + Self::CHAIN_TX_HASH_LOG_INDEX_INDEX, + &["chain", "transaction_hash", "log_index"], + true, + )?; + table.create_multi_index( + CHAIN_TOKEN_ADD_TOKEN_ID_INDEX, + &["chain", "token_address", "token_id"], + false, + )?; + table.create_multi_index(CHAIN_BLOCK_NUMBER_INDEX, &["chain", "block_number"], false)?; + table.create_multi_index(CHAIN_TOKEN_ADD_INDEX, &["chain", "token_address"], false)?; + table.create_multi_index(CHAIN_TOKEN_DOMAIN_INDEX, &["chain", "token_domain"], false)?; + table.create_multi_index(CHAIN_IMAGE_DOMAIN_INDEX, &["chain", "image_domain"], false)?; + table.create_index("block_number", false)?; + table.create_index("chain", false)?; + }, + 1 => { + let table = upgrader.open_table(Self::TABLE_NAME)?; + // When we change indexes during `onupgradeneeded`, IndexedDB automatically updates it with the existing records + table.create_multi_index( + Self::CHAIN_TX_HASH_LOG_INDEX_TOKEN_ID_INDEX, + &["chain", "transaction_hash", "log_index", "token_id"], + true, + )?; + table.delete_index(Self::CHAIN_TX_HASH_LOG_INDEX_INDEX)?; + }, + unsupported_version => { + return MmError::err(OnUpgradeError::UnsupportedVersion { + unsupported_version, + old_version, + new_version, + }) + }, + } + old_version += 1; } Ok(()) } @@ -1016,10 +1064,25 @@ pub(crate) struct LastScannedBlockTable { impl TableSignature for LastScannedBlockTable { const TABLE_NAME: &'static str = "last_scanned_block_table"; - fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - if is_initial_upgrade(old_version, new_version) { - let table = upgrader.create_table(Self::TABLE_NAME)?; - table.create_index("chain", true)?; + fn on_upgrade_needed(upgrader: &DbUpgrader, mut old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + while old_version < new_version { + match old_version { + 0 => { + let table = upgrader.create_table(Self::TABLE_NAME)?; + table.create_index("chain", true)?; + }, + 1 => { + // nothing to change + }, + unsupported_version => { + return MmError::err(OnUpgradeError::UnsupportedVersion { + unsupported_version, + old_version, + new_version, + }) + }, + } + old_version += 1; } Ok(()) } diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index 3ee9e7761b..df14fea09a 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -56,6 +56,7 @@ use serde_json::{self as json, Value as Json}; use serialization::{deserialize, serialize, CoinVariant}; use std::collections::{HashMap, HashSet}; use std::convert::TryInto; +use std::num::TryFromIntError; use std::ops::{Deref, Neg}; #[cfg(not(target_arch = "wasm32"))] use std::path::PathBuf; use std::str::FromStr; @@ -452,11 +453,11 @@ pub enum Qrc20AbiError { #[display(fmt = "Invalid QRC20 ABI params: {}", _0)] InvalidParams(String), #[display(fmt = "QRC20 ABI error: {}", _0)] - AbiError(String), + ABIError(String), } impl From for Qrc20AbiError { - fn from(e: ethabi::Error) -> Qrc20AbiError { Qrc20AbiError::AbiError(e.to_string()) } + fn from(e: ethabi::Error) -> Qrc20AbiError { Qrc20AbiError::ABIError(e.to_string()) } } impl From for ValidatePaymentError { @@ -758,52 +759,42 @@ impl UtxoCommonOps for Qrc20Coin { #[async_trait] impl SwapOps for Qrc20Coin { - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { - let to_address = try_tx_fus!(self.contract_address_from_raw_pubkey(fee_addr)); - let amount = try_tx_fus!(wei_from_big_decimal(&dex_fee.fee_amount().into(), self.utxo.decimals)); + async fn send_taker_fee( + &self, + fee_addr: &[u8], + dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { + let to_address = try_tx_s!(self.contract_address_from_raw_pubkey(fee_addr)); + let amount = try_tx_s!(wei_from_big_decimal(&dex_fee.fee_amount().into(), self.utxo.decimals)); let transfer_output = - try_tx_fus!(self.transfer_output(to_address, amount, QRC20_GAS_LIMIT_DEFAULT, QRC20_GAS_PRICE_DEFAULT)); - let outputs = vec![transfer_output]; - - let selfi = self.clone(); - let fut = async move { selfi.send_contract_calls(outputs).await }; - - Box::new(fut.boxed().compat()) + try_tx_s!(self.transfer_output(to_address, amount, QRC20_GAS_LIMIT_DEFAULT, QRC20_GAS_PRICE_DEFAULT)); + self.send_contract_calls(vec![transfer_output]).await } - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { - let time_lock = try_tx_fus!(maker_payment_args.time_lock.try_into()); - let taker_addr = try_tx_fus!(self.contract_address_from_raw_pubkey(maker_payment_args.other_pubkey)); + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + let time_lock = try_tx_s!(maker_payment_args.time_lock.try_into()); + let taker_addr = try_tx_s!(self.contract_address_from_raw_pubkey(maker_payment_args.other_pubkey)); let id = qrc20_swap_id(time_lock, maker_payment_args.secret_hash); - let value = try_tx_fus!(wei_from_big_decimal(&maker_payment_args.amount, self.utxo.decimals)); + let value = try_tx_s!(wei_from_big_decimal(&maker_payment_args.amount, self.utxo.decimals)); let secret_hash = Vec::from(maker_payment_args.secret_hash); - let swap_contract_address = try_tx_fus!(maker_payment_args.swap_contract_address.try_to_address()); + let swap_contract_address = try_tx_s!(maker_payment_args.swap_contract_address.try_to_address()); - let selfi = self.clone(); - let fut = async move { - selfi - .send_hash_time_locked_payment(id, value, time_lock, secret_hash, taker_addr, swap_contract_address) - .await - }; - Box::new(fut.boxed().compat()) + self.send_hash_time_locked_payment(id, value, time_lock, secret_hash, taker_addr, swap_contract_address) + .await } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { - let time_lock = try_tx_fus!(taker_payment_args.time_lock.try_into()); - let maker_addr = try_tx_fus!(self.contract_address_from_raw_pubkey(taker_payment_args.other_pubkey)); + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + let time_lock = try_tx_s!(taker_payment_args.time_lock.try_into()); + let maker_addr = try_tx_s!(self.contract_address_from_raw_pubkey(taker_payment_args.other_pubkey)); let id = qrc20_swap_id(time_lock, taker_payment_args.secret_hash); - let value = try_tx_fus!(wei_from_big_decimal(&taker_payment_args.amount, self.utxo.decimals)); + let value = try_tx_s!(wei_from_big_decimal(&taker_payment_args.amount, self.utxo.decimals)); let secret_hash = Vec::from(taker_payment_args.secret_hash); - let swap_contract_address = try_tx_fus!(taker_payment_args.swap_contract_address.try_to_address()); - - let selfi = self.clone(); - let fut = async move { - selfi - .send_hash_time_locked_payment(id, value, time_lock, secret_hash, maker_addr, swap_contract_address) - .await - }; - Box::new(fut.boxed().compat()) + let swap_contract_address = try_tx_s!(taker_payment_args.swap_contract_address.try_to_address()); + self.send_hash_time_locked_payment(id, value, time_lock, secret_hash, maker_addr, swap_contract_address) + .await } #[inline] @@ -855,39 +846,36 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { - let fee_tx = validate_fee_args.fee_tx; - let min_block_number = validate_fee_args.min_block_number; - let fee_tx = match fee_tx { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { + let fee_tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx, - _ => panic!("Unexpected TransactionEnum"), + fee_tx => { + return MmError::err(ValidatePaymentError::InternalError(format!( + "Invalid fee tx type. fee tx: {:?}", + fee_tx + ))) + }, }; let fee_tx_hash = fee_tx.hash().reversed().into(); - let inputs_signed_by_pub = try_f!(check_all_utxo_inputs_signed_by_pub( - fee_tx, - validate_fee_args.expected_sender - )); + let inputs_signed_by_pub = check_all_utxo_inputs_signed_by_pub(fee_tx, validate_fee_args.expected_sender)?; if !inputs_signed_by_pub { - return Box::new(futures01::future::err( - ValidatePaymentError::WrongPaymentTx("The dex fee was sent from wrong address".to_string()).into(), + return MmError::err(ValidatePaymentError::WrongPaymentTx( + "The dex fee was sent from wrong address".to_string(), )); } - let fee_addr = try_f!(self + let fee_addr = self .contract_address_from_raw_pubkey(validate_fee_args.fee_addr) - .map_to_mm(ValidatePaymentError::WrongPaymentTx)); - let expected_value = try_f!(wei_from_big_decimal( - &validate_fee_args.dex_fee.fee_amount().into(), - self.utxo.decimals - )); - - let selfi = self.clone(); - let fut = async move { - selfi - .validate_fee_impl(fee_tx_hash, fee_addr, expected_value, min_block_number) - .await - .map_to_mm(ValidatePaymentError::WrongPaymentTx) - }; - Box::new(fut.boxed().compat()) + .map_to_mm(ValidatePaymentError::WrongPaymentTx)?; + let expected_value = wei_from_big_decimal(&validate_fee_args.dex_fee.fee_amount().into(), self.utxo.decimals)?; + + self.validate_fee_impl( + fee_tx_hash, + fee_addr, + expected_value, + validate_fee_args.min_block_number, + ) + .await + .map_to_mm(ValidatePaymentError::WrongPaymentTx) } #[inline] @@ -943,24 +931,23 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, - ) -> Box, Error = String> + Send> { - let search_from_block = if_my_payment_sent_args.search_from_block; - let swap_id = qrc20_swap_id( - try_fus!(if_my_payment_sent_args.time_lock.try_into()), - if_my_payment_sent_args.secret_hash, - ); - let swap_contract_address = try_fus!(if_my_payment_sent_args.swap_contract_address.try_to_address()); + ) -> Result, String> { + let time_lock = if_my_payment_sent_args + .time_lock + .try_into() + .map_err(|e: TryFromIntError| e.to_string())?; + let swap_id = qrc20_swap_id(time_lock, if_my_payment_sent_args.secret_hash); + let swap_contract_address = if_my_payment_sent_args.swap_contract_address.try_to_address()?; - let selfi = self.clone(); - let fut = async move { - selfi - .check_if_my_payment_sent_impl(swap_contract_address, swap_id, search_from_block) - .await - }; - Box::new(fut.boxed().compat()) + self.check_if_my_payment_sent_impl( + swap_contract_address, + swap_id, + if_my_payment_sent_args.search_from_block, + ) + .await } #[inline] @@ -1267,23 +1254,11 @@ impl MarketCoinOps for Qrc20Coin { Box::new(fut.boxed().compat()) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { - let tx: UtxoTx = try_tx_fus!(deserialize(args.tx_bytes).map_err(|e| ERRL!("{:?}", e))); - - let selfi = self.clone(); - let WaitForHTLCTxSpendArgs { - check_every, - from_block, - wait_until, - .. - } = args; - let fut = async move { - selfi - .wait_for_tx_spend_impl(tx, wait_until, from_block, check_every) - .map_err(TransactionErr::Plain) - .await - }; - Box::new(fut.boxed().compat()) + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { + let tx: UtxoTx = try_tx_s!(deserialize(args.tx_bytes).map_err(|e| ERRL!("{:?}", e))); + self.wait_for_tx_spend_impl(tx, args.wait_until, args.from_block, args.check_every) + .map_err(TransactionErr::Plain) + .await } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index 2caf87c3bf..3e6dbc94dd 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -1,6 +1,6 @@ use super::*; use crate::{DexFee, TxFeeDetails, WaitForHTLCTxSpendArgs}; -use common::{block_on, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; +use common::{block_on, block_on_f01, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::Secp256k1Secret; use itertools::Itertools; use keys::Address; @@ -96,7 +96,7 @@ fn test_withdraw_to_p2sh_address_should_fail() { memo: None, ibc_source_channel: None, }; - let err = coin.withdraw(req).wait().unwrap_err().into_inner(); + let err = block_on_f01(coin.withdraw(req)).unwrap_err().into_inner(); let expect = WithdrawError::InvalidAddress("QRC20 can be sent to P2PKH addresses only".to_owned()); assert_eq!(err, expect); } @@ -112,19 +112,22 @@ fn test_withdraw_impl_fee_details() { let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); Qrc20Coin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let withdraw_req = WithdrawRequest { @@ -140,7 +143,7 @@ fn test_withdraw_impl_fee_details() { memo: None, ibc_source_channel: None, }; - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); let expected: Qrc20FeeDetails = json::from_value(json!({ "coin": "QTUM", @@ -287,7 +290,7 @@ fn test_wait_for_confirmations_excepted() { wait_until, check_every, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); // tx_hash: ed53b97deb2ad76974c972cb084f6ba63bd9f16c91c4a39106a20c6d14599b2a // `erc20Payment` contract call excepted @@ -299,7 +302,7 @@ fn test_wait_for_confirmations_excepted() { wait_until, check_every, }; - let error = coin.wait_for_confirmations(confirm_payment_input).wait().unwrap_err(); + let error = block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap_err(); log!("error: {:?}", error); assert!(error.contains("Contract call failed with an error: Revert")); @@ -313,7 +316,7 @@ fn test_wait_for_confirmations_excepted() { wait_until, check_every, }; - let error = coin.wait_for_confirmations(confirm_payment_input).wait().unwrap_err(); + let error = block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap_err(); log!("error: {:?}", error); assert!(error.contains("Contract call failed with an error: Revert")); } @@ -333,67 +336,59 @@ fn test_validate_fee() { let amount = BigDecimal::from_str("0.01").unwrap(); - let result = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &tx, - expected_sender: &sender_pub, - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(amount.clone().into()), - min_block_number: 0, - uuid: &[], - }) - .wait(); + let result = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &tx, + expected_sender: &sender_pub, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(amount.clone().into()), + min_block_number: 0, + uuid: &[], + })); assert!(result.is_ok()); let fee_addr_dif = hex::decode("03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc05").unwrap(); - let err = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &tx, - expected_sender: &sender_pub, - fee_addr: &fee_addr_dif, - dex_fee: &DexFee::Standard(amount.clone().into()), - min_block_number: 0, - uuid: &[], - }) - .wait() - .expect_err("Expected an error") - .into_inner(); + let err = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &tx, + expected_sender: &sender_pub, + fee_addr: &fee_addr_dif, + dex_fee: &DexFee::Standard(amount.clone().into()), + min_block_number: 0, + uuid: &[], + })) + .expect_err("Expected an error") + .into_inner(); log!("error: {:?}", err); match err { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("QRC20 Fee tx was sent to wrong address")), _ => panic!("Expected `WrongPaymentTx` wrong receiver address, found {:?}", err), } - let err = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &tx, - expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(amount.clone().into()), - min_block_number: 0, - uuid: &[], - }) - .wait() - .expect_err("Expected an error") - .into_inner(); + let err = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &tx, + expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(amount.clone().into()), + min_block_number: 0, + uuid: &[], + })) + .expect_err("Expected an error") + .into_inner(); log!("error: {:?}", err); match err { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("was sent from wrong address")), _ => panic!("Expected `WrongPaymentTx` wrong sender address, found {:?}", err), } - let err = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &tx, - expected_sender: &sender_pub, - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(amount.clone().into()), - min_block_number: 2000000, - uuid: &[], - }) - .wait() - .expect_err("Expected an error") - .into_inner(); + let err = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &tx, + expected_sender: &sender_pub, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(amount.clone().into()), + min_block_number: 2000000, + uuid: &[], + })) + .expect_err("Expected an error") + .into_inner(); log!("error: {:?}", err); match err { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), @@ -401,18 +396,16 @@ fn test_validate_fee() { } let amount_dif = BigDecimal::from_str("0.02").unwrap(); - let err = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &tx, - expected_sender: &sender_pub, - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(amount_dif.into()), - min_block_number: 0, - uuid: &[], - }) - .wait() - .expect_err("Expected an error") - .into_inner(); + let err = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &tx, + expected_sender: &sender_pub, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(amount_dif.into()), + min_block_number: 0, + uuid: &[], + })) + .expect_err("Expected an error") + .into_inner(); log!("error: {:?}", err); match err { ValidatePaymentError::WrongPaymentTx(err) => { @@ -424,18 +417,16 @@ fn test_validate_fee() { // QTUM tx "8a51f0ffd45f34974de50f07c5bf2f0949da4e88433f8f75191953a442cf9310" let tx = TransactionEnum::UtxoTx("020000000113640281c9332caeddd02a8dd0d784809e1ad87bda3c972d89d5ae41f5494b85010000006a47304402207c5c904a93310b8672f4ecdbab356b65dd869a426e92f1064a567be7ccfc61ff02203e4173b9467127f7de4682513a21efb5980e66dbed4da91dff46534b8e77c7ef012102baefe72b3591de2070c0da3853226b00f082d72daa417688b61cb18c1d543d1afeffffff020001b2c4000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88acbc4dd20c2f0000001976a9144208fa7be80dcf972f767194ad365950495064a488ac76e70800".into()); let sender_pub = hex::decode("02baefe72b3591de2070c0da3853226b00f082d72daa417688b61cb18c1d543d1a").unwrap(); - let err = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &tx, - expected_sender: &sender_pub, - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(amount.into()), - min_block_number: 0, - uuid: &[], - }) - .wait() - .expect_err("Expected an error") - .into_inner(); + let err = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &tx, + expected_sender: &sender_pub, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(amount.into()), + min_block_number: 0, + uuid: &[], + })) + .expect_err("Expected an error") + .into_inner(); log!("error: {:?}", err); match err { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Expected 'transfer' contract call")), @@ -462,18 +453,16 @@ fn test_wait_for_tx_spend_malicious() { let payment_tx = hex::decode("01000000016601daa208531d20532c460d0c86b74a275f4a126bbffcf4eafdf33835af2859010000006a47304402205825657548bc1b5acf3f4bb2f89635a02b04f3228cd08126e63c5834888e7ac402207ca05fa0a629a31908a97a508e15076e925f8e621b155312b7526a6666b06a76012103693bff1b39e8b5a306810023c29b95397eb395530b106b1820ea235fd81d9ce9ffffffff020000000000000000e35403a0860101284cc49b415b2a8620ad3b72361a5aeba5dffd333fb64750089d935a1ec974d6a91ef4f24ff6ba0000000000000000000000000000000000000000000000000000000001312d00000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc44000000000000000000000000783cf0be521101942da509846ea476e683aad8324b6b2e5444c2639cc0fb7bcea5afba3f3cdce239000000000000000000000000000000000000000000000000000000000000000000000000000000005f855c7614ba8b71f3544b93e2f681f996da519a98ace0107ac2203de400000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88ac415d855f").unwrap(); let wait_until = now_sec() + 1; let from_block = 696245; - let found = coin - .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &payment_tx, - secret_hash: &[], - wait_until, - from_block, - swap_contract_address: &coin.swap_contract_address(), - check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: false, - }) - .wait() - .unwrap(); + let found = block_on(coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &payment_tx, + secret_hash: &[], + wait_until, + from_block, + swap_contract_address: &coin.swap_contract_address(), + check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, + watcher_reward: false, + })) + .unwrap(); let spend_tx = match found { TransactionEnum::UtxoTx(tx) => tx, @@ -731,7 +720,7 @@ fn test_get_trade_fee() { // check if the coin's tx fee is expected check_tx_fee(&coin, ActualTxFee::FixedPerKb(EXPECTED_TX_FEE as u64)); - let actual_trade_fee = coin.get_trade_fee().wait().unwrap(); + let actual_trade_fee = block_on_f01(coin.get_trade_fee()).unwrap(); let expected_trade_fee_amount = big_decimal_from_sat( 2 * CONTRACT_CALL_GAS_FEE + SWAP_PAYMENT_GAS_FEE + EXPECTED_TX_FEE, coin.utxo.decimals, @@ -905,10 +894,8 @@ fn test_receiver_trade_preimage() { // check if the coin's tx fee is expected check_tx_fee(&coin, ActualTxFee::FixedPerKb(EXPECTED_TX_FEE as u64)); - let actual = coin - .get_receiver_trade_fee(FeeApproxStage::WithoutApprox) - .wait() - .expect("!get_receiver_trade_fee"); + let actual = + block_on_f01(coin.get_receiver_trade_fee(FeeApproxStage::WithoutApprox)).expect("!get_receiver_trade_fee"); // only one contract call should be included into the expected trade fee let expected_receiver_fee = big_decimal_from_sat(CONTRACT_CALL_GAS_FEE + EXPECTED_TX_FEE, coin.utxo.decimals); let expected = TradeFee { @@ -935,7 +922,7 @@ fn test_taker_fee_tx_fee() { spendable: BigDecimal::from(5u32), unspendable: BigDecimal::from(0u32), }; - assert_eq!(coin.my_balance().wait().expect("!my_balance"), expected_balance); + assert_eq!(block_on_f01(coin.my_balance()).expect("!my_balance"), expected_balance); let dex_fee_amount = BigDecimal::from(5u32); let actual = block_on(coin.get_fee_to_send_taker_fee( diff --git a/mm2src/coins/qrc20/swap.rs b/mm2src/coins/qrc20/swap.rs index a6162f6421..7370926684 100644 --- a/mm2src/coins/qrc20/swap.rs +++ b/mm2src/coins/qrc20/swap.rs @@ -346,7 +346,23 @@ impl Qrc20Coin { receiver, secret_hash, .. - } = try_s!(self.erc20_payment_details_from_tx(&tx).await); + } = try_s!( + retry_on_err!(async { self.erc20_payment_details_from_tx(&tx).await }) + .until_ready() + .repeat_every_secs(check_every) + .until_s(wait_until) + .inspect_err({ + let tx_hash = tx.hash().reversed(); + move |e| { + error!( + "Failed to retrieve QRC20 payment details from transaction {} \ + will retry in {} seconds. Error: {:?}", + tx_hash, check_every, e + ) + } + }) + .await + ); loop { // Try to find a 'receiverSpend' contract call. diff --git a/mm2src/coins/rpc_command/account_balance.rs b/mm2src/coins/rpc_command/account_balance.rs index c0cb1bf09b..7e6587b788 100644 --- a/mm2src/coins/rpc_command/account_balance.rs +++ b/mm2src/coins/rpc_command/account_balance.rs @@ -63,10 +63,10 @@ pub async fn account_balance( req: HDAccountBalanceRequest, ) -> MmResult { match lp_coinfind_or_err(&ctx, &req.coin).await? { - MmCoinEnum::UtxoCoin(utxo) => Ok(HDAccountBalanceResponseEnum::Single( + MmCoinEnum::UtxoCoin(utxo) => Ok(HDAccountBalanceResponseEnum::Map( utxo.account_balance_rpc(req.params).await?, )), - MmCoinEnum::QtumCoin(qtum) => Ok(HDAccountBalanceResponseEnum::Single( + MmCoinEnum::QtumCoin(qtum) => Ok(HDAccountBalanceResponseEnum::Map( qtum.account_balance_rpc(req.params).await?, )), MmCoinEnum::EthCoin(eth) => Ok(HDAccountBalanceResponseEnum::Map( diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index b62e572756..811ecb448e 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -1,7 +1,7 @@ //! RPCs to start/stop gas fee estimator and get estimated base and priority fee per gas -use crate::eth::{EthCoin, EthCoinType, FeeEstimatorContext, FeeEstimatorState, FeePerGasEstimated}; -use crate::{lp_coinfind_or_err, wei_to_gwei_decimal, AsyncMutex, CoinFindError, MmCoinEnum, NumConversError}; +use crate::eth::{wei_to_gwei_decimal, EthCoin, EthCoinType, FeeEstimatorContext, FeeEstimatorState, FeePerGasEstimated}; +use crate::{lp_coinfind_or_err, AsyncMutex, CoinFindError, MmCoinEnum, NumConversError}; use common::executor::{spawn_abortable, Timer}; use common::log::debug; use common::{HttpStatusCode, StatusCode}; @@ -66,22 +66,22 @@ impl TryFrom for FeePerGasEstimatedExt { fn try_from(fees: FeePerGasEstimated) -> Result { Ok(Self { - base_fee: wei_to_gwei_decimal!(fees.base_fee)?, + base_fee: wei_to_gwei_decimal(fees.base_fee)?, low: FeePerGasLevel { - max_fee_per_gas: wei_to_gwei_decimal!(fees.low.max_fee_per_gas)?, - max_priority_fee_per_gas: wei_to_gwei_decimal!(fees.low.max_priority_fee_per_gas)?, + max_fee_per_gas: wei_to_gwei_decimal(fees.low.max_fee_per_gas)?, + max_priority_fee_per_gas: wei_to_gwei_decimal(fees.low.max_priority_fee_per_gas)?, min_wait_time: fees.low.min_wait_time, max_wait_time: fees.low.max_wait_time, }, medium: FeePerGasLevel { - max_fee_per_gas: wei_to_gwei_decimal!(fees.medium.max_fee_per_gas)?, - max_priority_fee_per_gas: wei_to_gwei_decimal!(fees.medium.max_priority_fee_per_gas)?, + max_fee_per_gas: wei_to_gwei_decimal(fees.medium.max_fee_per_gas)?, + max_priority_fee_per_gas: wei_to_gwei_decimal(fees.medium.max_priority_fee_per_gas)?, min_wait_time: fees.medium.min_wait_time, max_wait_time: fees.medium.max_wait_time, }, high: FeePerGasLevel { - max_fee_per_gas: wei_to_gwei_decimal!(fees.high.max_fee_per_gas)?, - max_priority_fee_per_gas: wei_to_gwei_decimal!(fees.high.max_priority_fee_per_gas)?, + max_fee_per_gas: wei_to_gwei_decimal(fees.high.max_fee_per_gas)?, + max_priority_fee_per_gas: wei_to_gwei_decimal(fees.high.max_priority_fee_per_gas)?, min_wait_time: fees.high.min_wait_time, max_wait_time: fees.high.max_wait_time, }, diff --git a/mm2src/coins/rpc_command/get_new_address.rs b/mm2src/coins/rpc_command/get_new_address.rs index 9bba74cf75..35796de9c2 100644 --- a/mm2src/coins/rpc_command/get_new_address.rs +++ b/mm2src/coins/rpc_command/get_new_address.rs @@ -320,7 +320,7 @@ impl RpcTask for InitGetNewAddressTask { } match self.coin { - MmCoinEnum::UtxoCoin(ref utxo) => Ok(GetNewAddressResponseEnum::Single( + MmCoinEnum::UtxoCoin(ref utxo) => Ok(GetNewAddressResponseEnum::Map( get_new_address_helper( &self.ctx, utxo, @@ -330,7 +330,7 @@ impl RpcTask for InitGetNewAddressTask { ) .await?, )), - MmCoinEnum::QtumCoin(ref qtum) => Ok(GetNewAddressResponseEnum::Single( + MmCoinEnum::QtumCoin(ref qtum) => Ok(GetNewAddressResponseEnum::Map( get_new_address_helper( &self.ctx, qtum, @@ -362,10 +362,10 @@ pub async fn get_new_address( ) -> MmResult { let coin = lp_coinfind_or_err(&ctx, &req.coin).await?; match coin { - MmCoinEnum::UtxoCoin(utxo) => Ok(GetNewAddressResponseEnum::Single( + MmCoinEnum::UtxoCoin(utxo) => Ok(GetNewAddressResponseEnum::Map( utxo.get_new_address_rpc_without_conf(req.params).await?, )), - MmCoinEnum::QtumCoin(qtum) => Ok(GetNewAddressResponseEnum::Single( + MmCoinEnum::QtumCoin(qtum) => Ok(GetNewAddressResponseEnum::Map( qtum.get_new_address_rpc_without_conf(req.params).await?, )), MmCoinEnum::EthCoin(eth) => Ok(GetNewAddressResponseEnum::Map( @@ -472,7 +472,7 @@ pub(crate) mod common_impl { Ok(GetNewAddressResponse { new_address: HDAddressBalance { - address: address.to_string(), + address: coin.address_formatter()(&address), derivation_path: RpcDerivationPath(hd_address.derivation_path().clone()), chain, balance, @@ -510,13 +510,14 @@ pub(crate) mod common_impl { let address = hd_address.address(); let balance = coin.known_address_balance(&address).await?; - coin.prepare_addresses_for_balance_stream_if_enabled(HashSet::from([address.to_string()])) + let formatted_address = coin.address_formatter()(&address); + coin.prepare_addresses_for_balance_stream_if_enabled(HashSet::from([formatted_address.clone()])) .await .map_err(|e| GetNewAddressRpcError::FailedScripthashSubscription(e.to_string()))?; Ok(GetNewAddressResponse { new_address: HDAddressBalance { - address: address.to_string(), + address: formatted_address, derivation_path: RpcDerivationPath(hd_address.derivation_path().clone()), chain, balance, diff --git a/mm2src/coins/rpc_command/init_account_balance.rs b/mm2src/coins/rpc_command/init_account_balance.rs index 94745f65e5..39e92cb12a 100644 --- a/mm2src/coins/rpc_command/init_account_balance.rs +++ b/mm2src/coins/rpc_command/init_account_balance.rs @@ -73,10 +73,10 @@ impl RpcTask for InitAccountBalanceTask { _task_handle: InitAccountBalanceTaskHandleShared, ) -> Result> { match self.coin { - MmCoinEnum::UtxoCoin(ref utxo) => Ok(HDAccountBalanceEnum::Single( + MmCoinEnum::UtxoCoin(ref utxo) => Ok(HDAccountBalanceEnum::Map( utxo.init_account_balance_rpc(self.req.params.clone()).await?, )), - MmCoinEnum::QtumCoin(ref qtum) => Ok(HDAccountBalanceEnum::Single( + MmCoinEnum::QtumCoin(ref qtum) => Ok(HDAccountBalanceEnum::Map( qtum.init_account_balance_rpc(self.req.params.clone()).await?, )), MmCoinEnum::EthCoin(ref eth) => Ok(HDAccountBalanceEnum::Map( diff --git a/mm2src/coins/rpc_command/init_create_account.rs b/mm2src/coins/rpc_command/init_create_account.rs index cf0c819f6c..833998a1c3 100644 --- a/mm2src/coins/rpc_command/init_create_account.rs +++ b/mm2src/coins/rpc_command/init_create_account.rs @@ -286,7 +286,7 @@ impl RpcTask for InitCreateAccountTask { } match self.coin { - MmCoinEnum::UtxoCoin(ref utxo) => Ok(HDAccountBalanceEnum::Single( + MmCoinEnum::UtxoCoin(ref utxo) => Ok(HDAccountBalanceEnum::Map( create_new_account_helper( &self.ctx, utxo, @@ -298,7 +298,7 @@ impl RpcTask for InitCreateAccountTask { ) .await?, )), - MmCoinEnum::QtumCoin(ref qtum) => Ok(HDAccountBalanceEnum::Single( + MmCoinEnum::QtumCoin(ref qtum) => Ok(HDAccountBalanceEnum::Map( create_new_account_helper( &self.ctx, qtum, @@ -388,8 +388,7 @@ pub(crate) mod common_impl { use super::*; use crate::coin_balance::{HDWalletBalanceObject, HDWalletBalanceOps}; use crate::hd_wallet::{create_new_account, ExtractExtendedPubkey, HDAccountOps, HDAccountStorageOps, - HDCoinHDAccount, HDWalletOps}; - use crypto::Secp256k1ExtendedPublicKey; + HDCoinExtendedPubkey, HDCoinHDAccount, HDWalletOps}; pub async fn init_create_new_account_rpc<'a, Coin, XPubExtractor>( coin: &Coin, @@ -398,7 +397,7 @@ pub(crate) mod common_impl { xpub_extractor: Option, ) -> MmResult>, CreateAccountRpcError> where - Coin: ExtractExtendedPubkey + Coin: ExtractExtendedPubkey> + HDWalletBalanceOps + CoinWithDerivationMethod + Send diff --git a/mm2src/coins/rpc_command/init_scan_for_new_addresses.rs b/mm2src/coins/rpc_command/init_scan_for_new_addresses.rs index 4eabda46e9..eaf51277b6 100644 --- a/mm2src/coins/rpc_command/init_scan_for_new_addresses.rs +++ b/mm2src/coins/rpc_command/init_scan_for_new_addresses.rs @@ -92,10 +92,10 @@ impl RpcTask for InitScanAddressesTask { async fn run(&mut self, _task_handle: ScanAddressesTaskHandleShared) -> Result> { match self.coin { - MmCoinEnum::UtxoCoin(ref utxo) => Ok(ScanAddressesResponseEnum::Single( + MmCoinEnum::UtxoCoin(ref utxo) => Ok(ScanAddressesResponseEnum::Map( utxo.init_scan_for_new_addresses_rpc(self.req.params.clone()).await?, )), - MmCoinEnum::QtumCoin(ref qtum) => Ok(ScanAddressesResponseEnum::Single( + MmCoinEnum::QtumCoin(ref qtum) => Ok(ScanAddressesResponseEnum::Map( qtum.init_scan_for_new_addresses_rpc(self.req.params.clone()).await?, )), MmCoinEnum::EthCoin(ref eth) => Ok(ScanAddressesResponseEnum::Map( diff --git a/mm2src/coins/sia/address.rs b/mm2src/coins/sia/address.rs deleted file mode 100644 index 5218a08bc8..0000000000 --- a/mm2src/coins/sia/address.rs +++ /dev/null @@ -1,167 +0,0 @@ -use crate::sia::blake2b_internal::standard_unlock_hash; -use blake2b_simd::Params; -use ed25519_dalek::PublicKey; -use hex::FromHexError; -use rpc::v1::types::H256; -use serde::{Deserialize, Serialize}; -use std::convert::TryInto; -use std::fmt; -use std::str::FromStr; - -// TODO this could probably include the checksum within the data type -// generating the checksum on the fly is how Sia Go does this however -#[derive(Debug, Clone, PartialEq)] -pub struct Address(pub H256); - -impl Address { - pub fn str_without_prefix(&self) -> String { - let bytes = self.0 .0.as_ref(); - let checksum = blake2b_checksum(bytes); - format!("{}{}", hex::encode(bytes), hex::encode(checksum)) - } -} - -impl fmt::Display for Address { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "addr:{}", self.str_without_prefix()) } -} - -impl fmt::Display for ParseAddressError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Failed to parse Address: {:?}", self) } -} - -#[derive(Debug, Deserialize, Serialize)] -pub enum ParseAddressError { - #[serde(rename = "Address must begin with addr: prefix")] - MissingPrefix, - InvalidHexEncoding(String), - InvalidChecksum, - InvalidLength, - // Add other error kinds as needed -} - -impl From for ParseAddressError { - fn from(e: FromHexError) -> Self { ParseAddressError::InvalidHexEncoding(e.to_string()) } -} - -impl FromStr for Address { - type Err = ParseAddressError; - - fn from_str(s: &str) -> Result { - if !s.starts_with("addr:") { - return Err(ParseAddressError::MissingPrefix); - } - - let without_prefix = &s[5..]; - if without_prefix.len() != (32 + 6) * 2 { - return Err(ParseAddressError::InvalidLength); - } - - let (address_hex, checksum_hex) = without_prefix.split_at(32 * 2); - - let address_bytes: [u8; 32] = hex::decode(address_hex) - .map_err(ParseAddressError::from)? - .try_into() - .expect("length is 32 bytes"); - - let checksum = hex::decode(checksum_hex).map_err(ParseAddressError::from)?; - let checksum_bytes: [u8; 6] = checksum.try_into().expect("length is 6 bytes"); - - if checksum_bytes != blake2b_checksum(&address_bytes) { - return Err(ParseAddressError::InvalidChecksum); - } - - Ok(Address(H256::from(address_bytes))) - } -} - -// Sia uses the first 6 bytes of blake2b(preimage) appended -// to address as checksum -fn blake2b_checksum(preimage: &[u8]) -> [u8; 6] { - let hash = Params::new().hash_length(32).to_state().update(preimage).finalize(); - hash.as_array()[0..6].try_into().expect("array is 64 bytes long") -} - -pub fn v1_standard_address_from_pubkey(pubkey: &PublicKey) -> Address { - let hash = standard_unlock_hash(pubkey); - Address(hash) -} - -#[test] -fn test_v1_standard_address_from_pubkey() { - let pubkey = PublicKey::from_bytes( - &hex::decode("8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c").unwrap(), - ) - .unwrap(); - let address = v1_standard_address_from_pubkey(&pubkey); - assert_eq!( - format!("{}", address), - "addr:c959f9b423b662c36ee58057b8157acedb4095cfeb7926e4ba44cd9ee1f49a5b7803c7501a7b" - ) -} - -#[test] -fn test_blake2b_checksum() { - let checksum = - blake2b_checksum(&hex::decode("591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a884").unwrap()); - let expected: [u8; 6] = hex::decode("0be0653e411f").unwrap().try_into().unwrap(); - assert_eq!(checksum, expected); -} - -#[test] -fn test_address_display() { - let address = Address("591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a884".into()); - let address_str = "addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f"; - assert_eq!(format!("{}", address), address_str); -} - -#[test] -fn test_address_fromstr() { - let address1 = Address("591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a884".into()); - - let address2 = - Address::from_str("addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f").unwrap(); - assert_eq!(address1, address2); -} - -#[test] -fn test_address_fromstr_bad_length() { - let address = Address::from_str("addr:dead"); - assert!(matches!(address, Err(ParseAddressError::InvalidLength))); -} - -#[test] -fn test_address_fromstr_odd_length() { - let address = Address::from_str("addr:f00"); - assert!(matches!(address, Err(ParseAddressError::InvalidLength))); -} - -#[test] -fn test_address_fromstr_invalid_hex() { - let address = - Address::from_str("addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e41gg"); - assert!(matches!(address, Err(ParseAddressError::InvalidHexEncoding(_)))); -} - -#[test] -fn test_address_fromstr_missing_prefix() { - let address = Address::from_str("591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e41gg"); - assert!(matches!(address, Err(ParseAddressError::MissingPrefix))); -} - -#[test] -fn test_address_fromstr_invalid_checksum() { - let address = - Address::from_str("addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a884ffffffffffff"); - assert!(matches!(address, Err(ParseAddressError::InvalidChecksum))); -} - -#[test] -fn test_address_str_without_prefix() { - let address = - Address::from_str("addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f").unwrap(); - - assert_eq!( - address.str_without_prefix(), - "591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f" - ); -} diff --git a/mm2src/coins/sia/blake2b_internal.rs b/mm2src/coins/sia/blake2b_internal.rs deleted file mode 100644 index 39c4f7c82b..0000000000 --- a/mm2src/coins/sia/blake2b_internal.rs +++ /dev/null @@ -1,326 +0,0 @@ -use blake2b_simd::Params; -use ed25519_dalek::PublicKey; -use rpc::v1::types::H256; -use std::default::Default; - -#[cfg(test)] use hex; -#[cfg(test)] use std::convert::TryInto; - -const LEAF_HASH_PREFIX: [u8; 1] = [0u8]; -const NODE_HASH_PREFIX: [u8; 1] = [1u8]; - -pub const ED25519_IDENTIFIER: [u8; 16] = [ - 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, -]; - -// Precomputed hash values used for all standard v1 addresses -// a standard address has 1 ed25519 public key, requires 1 signature and has a timelock of 0 -// https://github.com/SiaFoundation/core/blob/b5b08cde6b7d0f1b3a6f09b8aa9d0b817e769efb/types/hash.go#L94 -const STANDARD_TIMELOCK_BLAKE2B_HASH: [u8; 32] = [ - 0x51, 0x87, 0xb7, 0xa8, 0x02, 0x1b, 0xf4, 0xf2, 0xc0, 0x04, 0xea, 0x3a, 0x54, 0xcf, 0xec, 0xe1, 0x75, 0x4f, 0x11, - 0xc7, 0x62, 0x4d, 0x23, 0x63, 0xc7, 0xf4, 0xcf, 0x4f, 0xdd, 0xd1, 0x44, 0x1e, -]; - -const STANDARD_SIGS_REQUIRED_BLAKE2B_HASH: [u8; 32] = [ - 0xb3, 0x60, 0x10, 0xeb, 0x28, 0x5c, 0x15, 0x4a, 0x8c, 0xd6, 0x30, 0x84, 0xac, 0xbe, 0x7e, 0xac, 0x0c, 0x4d, 0x62, - 0x5a, 0xb4, 0xe1, 0xa7, 0x6e, 0x62, 0x4a, 0x87, 0x98, 0xcb, 0x63, 0x49, 0x7b, -]; - -#[derive(Debug, PartialEq)] -pub struct Accumulator { - trees: [H256; 64], - num_leaves: u64, -} - -impl Default for Accumulator { - fn default() -> Self { - Accumulator { - trees: [H256::default(); 64], // Initialize all bytes to zero - num_leaves: 0, - } - } -} - -impl Accumulator { - // Check if there is a tree at the given height - fn has_tree_at_height(&self, height: u64) -> bool { self.num_leaves & (1 << height) != 0 } - - // Add a leaf to the accumulator - pub fn add_leaf(&mut self, h: H256) { - let mut i = 0; - let mut new_hash = h; - while self.has_tree_at_height(i) { - new_hash = hash_blake2b_pair(&NODE_HASH_PREFIX, &self.trees[i as usize].0, &new_hash.0); - i += 1; - } - self.trees[i as usize] = new_hash; - self.num_leaves += 1; - } - - // Calulate the root hash of the Merkle tree - pub fn root(&self) -> H256 { - // trailing_zeros determines the height Merkle tree accumulator where the current lowest single leaf is located - let i = self.num_leaves.trailing_zeros() as u64; - if i == 64 { - return H256::default(); // Return all zeros if no leaves - } - let mut root = self.trees[i as usize]; - for j in i + 1..64 { - if self.has_tree_at_height(j) { - root = hash_blake2b_pair(&NODE_HASH_PREFIX, &self.trees[j as usize].0, &root.0); - } - } - root - } -} - -pub fn sigs_required_leaf(sigs_required: u64) -> H256 { - let sigs_required_array: [u8; 8] = sigs_required.to_le_bytes(); - let mut combined = Vec::new(); - combined.extend_from_slice(&LEAF_HASH_PREFIX); - combined.extend_from_slice(&sigs_required_array); - - hash_blake2b_single(&combined) -} - -// public key leaf is -// blake2b(leafHashPrefix + 16_byte_ascii_algorithm_identifier + public_key_length_u64 + public_key) -pub fn public_key_leaf(pubkey: &PublicKey) -> H256 { - let mut combined = Vec::new(); - combined.extend_from_slice(&LEAF_HASH_PREFIX); - combined.extend_from_slice(&ED25519_IDENTIFIER); - combined.extend_from_slice(&32u64.to_le_bytes()); - combined.extend_from_slice(pubkey.as_bytes()); - hash_blake2b_single(&combined) -} - -pub fn timelock_leaf(timelock: u64) -> H256 { - let timelock: [u8; 8] = timelock.to_le_bytes(); - let mut combined = Vec::new(); - combined.extend_from_slice(&LEAF_HASH_PREFIX); - combined.extend_from_slice(&timelock); - - hash_blake2b_single(&combined) -} - -// https://github.com/SiaFoundation/core/blob/b5b08cde6b7d0f1b3a6f09b8aa9d0b817e769efb/types/hash.go#L96 -// An UnlockHash is the Merkle root of UnlockConditions. Since the standard -// UnlockConditions use a single public key, the Merkle tree is: -// -// ┌─────────┴──────────┐ -// ┌─────┴─────┐ │ -// timelock pubkey sigsrequired -pub fn standard_unlock_hash(pubkey: &PublicKey) -> H256 { - let pubkey_leaf = public_key_leaf(pubkey); - let timelock_pubkey_node = hash_blake2b_pair(&NODE_HASH_PREFIX, &STANDARD_TIMELOCK_BLAKE2B_HASH, &pubkey_leaf.0); - hash_blake2b_pair( - &NODE_HASH_PREFIX, - &timelock_pubkey_node.0, - &STANDARD_SIGS_REQUIRED_BLAKE2B_HASH, - ) -} - -pub fn hash_blake2b_single(preimage: &[u8]) -> H256 { - let hash = Params::new().hash_length(32).to_state().update(preimage).finalize(); - let ret_array = hash.as_array(); - ret_array[0..32].into() -} - -fn hash_blake2b_pair(prefix: &[u8], leaf1: &[u8], leaf2: &[u8]) -> H256 { - let hash = Params::new() - .hash_length(32) - .to_state() - .update(prefix) - .update(leaf1) - .update(leaf2) - .finalize(); - let ret_array = hash.as_array(); - ret_array[0..32].into() -} - -#[test] -fn test_accumulator_new() { - let default_accumulator = Accumulator::default(); - - let expected = Accumulator { - trees: [H256::from("0000000000000000000000000000000000000000000000000000000000000000"); 64], - num_leaves: 0, - }; - assert_eq!(default_accumulator, expected) -} - -#[test] -fn test_accumulator_root_default() { assert_eq!(Accumulator::default().root(), H256::default()) } - -#[test] -fn test_accumulator_root() { - let mut accumulator = Accumulator::default(); - - let timelock_leaf = timelock_leaf(0u64); - accumulator.add_leaf(timelock_leaf); - - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey_leaf = public_key_leaf(&pubkey); - accumulator.add_leaf(pubkey_leaf); - - let sigs_required_leaf = sigs_required_leaf(1u64); - accumulator.add_leaf(sigs_required_leaf); - - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(accumulator.root(), expected); -} - -#[test] -fn test_accumulator_add_leaf_standard_unlock_hash() { - let mut accumulator = Accumulator::default(); - - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - - let pubkey_leaf = public_key_leaf(&pubkey); - let timelock_leaf = timelock_leaf(0u64); - let sigs_required_leaf = sigs_required_leaf(1u64); - - accumulator.add_leaf(timelock_leaf); - accumulator.add_leaf(pubkey_leaf); - accumulator.add_leaf(sigs_required_leaf); - - let root = accumulator.root(); - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(root, expected) -} - -#[test] -fn test_accumulator_add_leaf_2of2_multisig_unlock_hash() { - let mut accumulator = Accumulator::default(); - - let pubkey1 = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey2 = PublicKey::from_bytes( - &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - - let pubkey1_leaf = public_key_leaf(&pubkey1); - let pubkey2_leaf = public_key_leaf(&pubkey2); - - let timelock_leaf = timelock_leaf(0u64); - let sigs_required_leaf = sigs_required_leaf(2u64); - - accumulator.add_leaf(timelock_leaf); - accumulator.add_leaf(pubkey1_leaf); - accumulator.add_leaf(pubkey2_leaf); - accumulator.add_leaf(sigs_required_leaf); - - let root = accumulator.root(); - let expected = H256::from("1e94357817d236167e54970a8c08bbd41b37bfceeeb52f6c1ce6dd01d50ea1e7"); - assert_eq!(root, expected) -} - -#[test] -fn test_accumulator_add_leaf_1of2_multisig_unlock_hash() { - let mut accumulator = Accumulator::default(); - - let pubkey1 = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey2 = PublicKey::from_bytes( - &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - - let pubkey1_leaf = public_key_leaf(&pubkey1); - let pubkey2_leaf = public_key_leaf(&pubkey2); - - let timelock_leaf = timelock_leaf(0u64); - let sigs_required_leaf = sigs_required_leaf(1u64); - - accumulator.add_leaf(timelock_leaf); - accumulator.add_leaf(pubkey1_leaf); - accumulator.add_leaf(pubkey2_leaf); - accumulator.add_leaf(sigs_required_leaf); - - let root = accumulator.root(); - let expected = H256::from("d7f84e3423da09d111a17f64290c8d05e1cbe4cab2b6bed49e3a4d2f659f0585"); - assert_eq!(root, expected) -} - -#[test] -fn test_standard_unlock_hash() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - - let hash = standard_unlock_hash(&pubkey); - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(hash, expected) -} - -#[test] -fn test_hash_blake2b_pair() { - let left: [u8; 32] = hex::decode("cdcce3978a58ceb6c8480d218646db4eae85eb9ea9c2f5138fbacb4ce2c701e3") - .unwrap() - .try_into() - .unwrap(); - let right: [u8; 32] = hex::decode("b36010eb285c154a8cd63084acbe7eac0c4d625ab4e1a76e624a8798cb63497b") - .unwrap() - .try_into() - .unwrap(); - - let hash = hash_blake2b_pair(&NODE_HASH_PREFIX, &left, &right); - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(hash, expected) -} - -#[test] -fn test_create_ed25519_identifier() { - let mut ed25519_identifier: [u8; 16] = [0; 16]; - - let bytes = "ed25519".as_bytes(); - for (i, &byte) in bytes.iter().enumerate() { - ed25519_identifier[i] = byte; - } - assert_eq!(ed25519_identifier, ED25519_IDENTIFIER); -} - -#[test] -fn test_timelock_leaf() { - let hash = timelock_leaf(0); - let expected = H256::from(STANDARD_TIMELOCK_BLAKE2B_HASH); - assert_eq!(hash, expected) -} - -#[test] -fn test_sigs_required_leaf() { - let hash = sigs_required_leaf(1u64); - let expected = H256::from(STANDARD_SIGS_REQUIRED_BLAKE2B_HASH); - assert_eq!(hash, expected) -} - -#[test] -fn test_hash_blake2b_single() { - let hash = hash_blake2b_single(&hex::decode("006564323535313900000000000000000020000000000000000102030000000000000000000000000000000000000000000000000000000000").unwrap()); - let expected = H256::from("21ce940603a2ee3a283685f6bfb4b122254894fd1ed3eb59434aadbf00c75d5b"); - assert_eq!(hash, expected) -} - -#[test] -fn test_public_key_leaf() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - - let hash = public_key_leaf(&pubkey); - let expected = H256::from("21ce940603a2ee3a283685f6bfb4b122254894fd1ed3eb59434aadbf00c75d5b"); - assert_eq!(hash, expected) -} diff --git a/mm2src/coins/sia/encoding.rs b/mm2src/coins/sia/encoding.rs deleted file mode 100644 index 5b6516101a..0000000000 --- a/mm2src/coins/sia/encoding.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::sia::blake2b_internal::hash_blake2b_single; -use rpc::v1::types::H256; - -// https://github.com/SiaFoundation/core/blob/092850cc52d3d981b19c66cd327b5d945b3c18d3/types/encoding.go#L16 -// TODO go implementation limits this to 1024 bytes, should we? -#[derive(Default)] -pub struct Encoder { - pub buffer: Vec, -} - -impl Encoder { - pub fn reset(&mut self) { self.buffer.clear(); } - - /// writes a length-prefixed []byte to the underlying stream. - pub fn write_len_prefixed_bytes(&mut self, data: &[u8]) { - self.buffer.extend_from_slice(&data.len().to_le_bytes()); - self.buffer.extend_from_slice(data); - } - - pub fn write_slice(&mut self, data: &[u8]) { self.buffer.extend_from_slice(data); } - - pub fn write_u8(&mut self, u: u8) { self.buffer.extend_from_slice(&[u]) } - - pub fn write_u64(&mut self, u: u64) { self.buffer.extend_from_slice(&u.to_le_bytes()); } - - pub fn write_distinguisher(&mut self, p: &str) { self.buffer.extend_from_slice(format!("sia/{}|", p).as_bytes()); } - - pub fn write_bool(&mut self, b: bool) { self.buffer.push(b as u8) } - - pub fn hash(&self) -> H256 { hash_blake2b_single(&self.buffer) } -} - -#[test] -fn test_encoder_default_hash() { - assert_eq!( - Encoder::default().hash(), - H256::from("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8") - ) -} - -#[test] -fn test_encoder_write_bytes() { - let mut encoder = Encoder::default(); - encoder.write_len_prefixed_bytes(&[1, 2, 3, 4]); - assert_eq!( - encoder.hash(), - H256::from("d4a72b52e2e1f40e20ee40ea6d5080a1b1f76164786defbb7691a4427f3388f5") - ); -} - -#[test] -fn test_encoder_write_u8() { - let mut encoder = Encoder::default(); - encoder.write_u8(1); - assert_eq!( - encoder.hash(), - H256::from("ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25") - ); -} - -#[test] -fn test_encoder_write_u64() { - let mut encoder = Encoder::default(); - encoder.write_u64(1); - assert_eq!( - encoder.hash(), - H256::from("1dbd7d0b561a41d23c2a469ad42fbd70d5438bae826f6fd607413190c37c363b") - ); -} - -#[test] -fn test_encoder_write_distiguisher() { - let mut encoder = Encoder::default(); - encoder.write_distinguisher("test"); - assert_eq!( - encoder.hash(), - H256::from("25fb524721bf98a9a1233a53c40e7e198971b003bf23c24f59d547a1bb837f9c") - ); -} - -#[test] -fn test_encoder_write_bool() { - let mut encoder = Encoder::default(); - encoder.write_bool(true); - assert_eq!( - encoder.hash(), - H256::from("ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25") - ); -} - -#[test] -fn test_encoder_reset() { - let mut encoder = Encoder::default(); - encoder.write_bool(true); - assert_eq!( - encoder.hash(), - H256::from("ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25") - ); - - encoder.reset(); - encoder.write_bool(false); - assert_eq!( - encoder.hash(), - H256::from("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314") - ); -} - -#[test] -fn test_encoder_complex() { - let mut encoder = Encoder::default(); - encoder.write_distinguisher("test"); - encoder.write_bool(true); - encoder.write_u8(1); - encoder.write_len_prefixed_bytes(&[1, 2, 3, 4]); - assert_eq!( - encoder.hash(), - H256::from("b66d7a9bef9fb303fe0e41f6b5c5af410303e428c4ff9231f6eb381248693221") - ); -} diff --git a/mm2src/coins/sia/http_client.rs b/mm2src/coins/sia/http_client.rs deleted file mode 100644 index 774dcd08e1..0000000000 --- a/mm2src/coins/sia/http_client.rs +++ /dev/null @@ -1,208 +0,0 @@ -use crate::sia::address::Address; -use crate::sia::SiaHttpConf; -use base64::engine::general_purpose::STANDARD as BASE64; -use base64::Engine as _; // required for .encode() method -use core::fmt::Display; -use core::time::Duration; -use mm2_number::MmNumber; -use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; -use reqwest::{Client, Error, Url}; -use serde::de::DeserializeOwned; -use std::ops::Deref; -use std::sync::Arc; - -#[cfg(test)] use std::str::FromStr; - -const ENDPOINT_CONSENSUS_TIP: &str = "api/consensus/tip"; - -/// HTTP(s) client for Sia-protocol coins -#[derive(Debug)] -pub struct SiaHttpClientImpl { - /// Name of coin the http client is intended to work with - pub coin_ticker: String, - /// The uri to send requests to - pub uri: String, - /// Value of Authorization header password, e.g. "Basic base64(:password)" - pub auth: String, -} - -#[derive(Clone, Debug)] -pub struct SiaApiClient(pub Arc); - -impl Deref for SiaApiClient { - type Target = SiaApiClientImpl; - fn deref(&self) -> &SiaApiClientImpl { &self.0 } -} - -impl SiaApiClient { - pub fn new(_coin_ticker: &str, http_conf: SiaHttpConf) -> Result { - let new_arc = SiaApiClientImpl::new(http_conf.url, &http_conf.auth)?; - Ok(SiaApiClient(Arc::new(new_arc))) - } -} - -#[derive(Debug)] -pub struct SiaApiClientImpl { - client: Client, - base_url: Url, -} - -// this is neccesary to show the URL in error messages returned to the user -// this can be removed in favor of using ".with_url()" once reqwest is updated to v0.11.23 -#[derive(Debug)] -pub struct ReqwestErrorWithUrl { - error: Error, - url: Url, -} - -impl Display for ReqwestErrorWithUrl { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "Error: {}, URL: {}", self.error, self.url) - } -} - -#[derive(Debug, Display)] -pub enum SiaApiClientError { - Timeout(String), - BuildError(String), - ApiUnreachable(String), - ReqwestError(ReqwestErrorWithUrl), - UrlParse(url::ParseError), -} - -impl From for String { - fn from(e: SiaApiClientError) -> Self { format!("{:?}", e) } -} - -async fn fetch_and_parse(client: &Client, url: Url) -> Result { - client - .get(url.clone()) - .send() - .await - .map_err(|e| { - SiaApiClientError::ReqwestError(ReqwestErrorWithUrl { - error: e, - url: url.clone(), - }) - })? - .json::() - .await - .map_err(|e| SiaApiClientError::ReqwestError(ReqwestErrorWithUrl { error: e, url })) -} - -// https://github.com/SiaFoundation/core/blob/4e46803f702891e7a83a415b7fcd7543b13e715e/types/types.go#L181 -#[derive(Deserialize, Serialize, Debug)] -pub struct GetConsensusTipResponse { - pub height: u64, - pub id: String, // TODO this can match "BlockID" type -} - -// https://github.com/SiaFoundation/walletd/blob/9574e69ff0bf84de1235b68e78db2a41d5e27516/api/api.go#L36 -// https://github.com/SiaFoundation/walletd/blob/9574e69ff0bf84de1235b68e78db2a41d5e27516/wallet/wallet.go#L25 -#[derive(Deserialize, Serialize, Debug)] -pub struct GetAddressesBalanceResponse { - pub siacoins: MmNumber, - #[serde(rename = "immatureSiacoins")] - pub immature_siacoins: MmNumber, - pub siafunds: u64, -} - -impl SiaApiClientImpl { - fn new(base_url: Url, password: &str) -> Result { - let mut headers = HeaderMap::new(); - let auth_value = format!("Basic {}", BASE64.encode(format!(":{}", password))); - headers.insert( - AUTHORIZATION, - HeaderValue::from_str(&auth_value).map_err(|e| SiaApiClientError::BuildError(e.to_string()))?, - ); - - let client = Client::builder() - .default_headers(headers) - .timeout(Duration::from_secs(10)) // TODO make this configurable - .build() - .map_err(|e| { - SiaApiClientError::ReqwestError(ReqwestErrorWithUrl { - error: e, - url: base_url.clone(), - }) - })?; - Ok(SiaApiClientImpl { client, base_url }) - } - - pub async fn get_consensus_tip(&self) -> Result { - let base_url = self.base_url.clone(); - let endpoint_url = base_url - .join(ENDPOINT_CONSENSUS_TIP) - .map_err(SiaApiClientError::UrlParse)?; - - fetch_and_parse::(&self.client, endpoint_url).await - } - - pub async fn get_addresses_balance( - &self, - address: &Address, - ) -> Result { - self.get_addresses_balance_str(&address.str_without_prefix()).await - } - - // use get_addresses_balance whenever possible to rely on Address deserialization - pub async fn get_addresses_balance_str( - &self, - address: &str, - ) -> Result { - let base_url = self.base_url.clone(); - - let endpoint_path = format!("api/addresses/{}/balance", address); - let endpoint_url = base_url.join(&endpoint_path).map_err(SiaApiClientError::UrlParse)?; - - fetch_and_parse::(&self.client, endpoint_url).await - } - - pub async fn get_height(&self) -> Result { - let resp = self.get_consensus_tip().await?; - Ok(resp.height) - } -} - -#[tokio::test] -#[ignore] -async fn test_api_client_timeout() { - let api_client = SiaApiClientImpl::new(Url::parse("http://foo").unwrap(), "password").unwrap(); - let result = api_client.get_consensus_tip().await; - assert!(matches!(result, Err(SiaApiClientError::Timeout(_)))); -} - -// TODO all of the following must be adapted to use Docker Sia node -#[tokio::test] -#[ignore] -async fn test_api_client_invalid_auth() { - let api_client = SiaApiClientImpl::new(Url::parse("http://127.0.0.1:9980").unwrap(), "password").unwrap(); - let result = api_client.get_consensus_tip().await; - assert!(matches!(result, Err(SiaApiClientError::BuildError(_)))); -} - -// TODO must be adapted to use Docker Sia node -#[tokio::test] -#[ignore] -async fn test_api_client() { - let api_client = SiaApiClientImpl::new(Url::parse("http://127.0.0.1:9980").unwrap(), "password").unwrap(); - let _result = api_client.get_consensus_tip().await.unwrap(); -} - -#[tokio::test] -#[ignore] -async fn test_api_get_addresses_balance() { - let api_client = SiaApiClientImpl::new(Url::parse("http://127.0.0.1:9980").unwrap(), "password").unwrap(); - let address = - Address::from_str("addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f").unwrap(); - let result = api_client.get_addresses_balance(&address).await.unwrap(); - println!("ret {:?}", result); -} - -#[tokio::test] -#[ignore] -async fn test_api_get_addresses_balance_invalid() { - let api_client = SiaApiClientImpl::new(Url::parse("http://127.0.0.1:9980").unwrap(), "password").unwrap(); - let result = api_client.get_addresses_balance_str("what").await.unwrap(); - println!("ret {:?}", result); -} diff --git a/mm2src/coins/sia/spend_policy.rs b/mm2src/coins/sia/spend_policy.rs deleted file mode 100644 index 6c28f7250a..0000000000 --- a/mm2src/coins/sia/spend_policy.rs +++ /dev/null @@ -1,322 +0,0 @@ -use crate::sia::address::Address; -use crate::sia::blake2b_internal::{public_key_leaf, sigs_required_leaf, standard_unlock_hash, timelock_leaf, - Accumulator, ED25519_IDENTIFIER}; -use crate::sia::encoding::Encoder; -use ed25519_dalek::PublicKey; -use rpc::v1::types::H256; - -#[cfg(test)] use std::str::FromStr; - -const POLICY_VERSION: u8 = 1u8; - -#[derive(Debug, Clone)] -pub enum SpendPolicy { - Above(u64), - After(u64), - PublicKey(PublicKey), - Hash(H256), - Threshold(PolicyTypeThreshold), - Opaque(Address), - UnlockConditions(PolicyTypeUnlockConditions), // For v1 compatibility -} - -impl SpendPolicy { - pub fn to_u8(&self) -> u8 { - match self { - SpendPolicy::Above(_) => 1, - SpendPolicy::After(_) => 2, - SpendPolicy::PublicKey(_) => 3, - SpendPolicy::Hash(_) => 4, - SpendPolicy::Threshold(_) => 5, - SpendPolicy::Opaque(_) => 6, - SpendPolicy::UnlockConditions(_) => 7, - } - } - - pub fn encode(&self) -> Encoder { - let mut encoder = Encoder::default(); - encoder.write_u8(POLICY_VERSION); - encoder.write_slice(&self.encode_wo_prefix().buffer); - encoder - } - - pub fn encode_wo_prefix(&self) -> Encoder { - let mut encoder = Encoder::default(); - let opcode = self.to_u8(); - match self { - SpendPolicy::Above(height) => { - encoder.write_u8(opcode); - encoder.write_u64(*height); - }, - SpendPolicy::After(time) => { - encoder.write_u8(opcode); - encoder.write_u64(*time); - }, - SpendPolicy::PublicKey(pubkey) => { - encoder.write_u8(opcode); - encoder.write_slice(&pubkey.to_bytes()); - }, - SpendPolicy::Hash(hash) => { - encoder.write_u8(opcode); - encoder.write_slice(&hash.0); - }, - SpendPolicy::Threshold(PolicyTypeThreshold { n, of }) => { - encoder.write_u8(opcode); - encoder.write_u8(*n); - encoder.write_u8(of.len() as u8); - for policy in of { - encoder.write_slice(&policy.encode_wo_prefix().buffer); - } - }, - SpendPolicy::Opaque(p) => { - encoder.write_u8(opcode); - encoder.write_slice(&p.0 .0); - }, - SpendPolicy::UnlockConditions(PolicyTypeUnlockConditions(unlock_condition)) => { - encoder.write_u8(opcode); - encoder.write_u64(unlock_condition.timelock); - encoder.write_u64(unlock_condition.pubkeys.len() as u64); - for pubkey in &unlock_condition.pubkeys { - encoder.write_slice(&ED25519_IDENTIFIER); - encoder.write_slice(&pubkey.to_bytes()); - } - encoder.write_u64(unlock_condition.sigs_required); - }, - } - encoder - } - - fn address(&self) -> Address { - if let SpendPolicy::UnlockConditions(PolicyTypeUnlockConditions(unlock_condition)) = self { - return unlock_condition.address(); - } - let mut encoder = Encoder::default(); - encoder.write_distinguisher("address"); - - // if self is a threshold policy, we need to convert all of its subpolicies to opaque - let mut new_policy = self.clone(); - if let SpendPolicy::Threshold(ref mut p) = new_policy { - p.of = p.of.iter().map(SpendPolicy::opaque).collect(); - } - - let encoded_policy = new_policy.encode(); - encoder.write_slice(&encoded_policy.buffer); - Address(encoder.hash()) - } - - pub fn above(height: u64) -> Self { SpendPolicy::Above(height) } - - pub fn after(time: u64) -> Self { SpendPolicy::After(time) } - - pub fn public_key(pk: PublicKey) -> Self { SpendPolicy::PublicKey(pk) } - - pub fn hash(h: H256) -> Self { SpendPolicy::Hash(h) } - - pub fn threshold(n: u8, of: Vec) -> Self { SpendPolicy::Threshold(PolicyTypeThreshold { n, of }) } - - pub fn opaque(p: &SpendPolicy) -> Self { SpendPolicy::Opaque(p.address()) } - - pub fn anyone_can_spend() -> Self { SpendPolicy::threshold(0, vec![]) } -} - -#[derive(Debug, Clone)] -pub struct PolicyTypeThreshold { - pub n: u8, - pub of: Vec, -} - -// Compatibility with Sia's "UnlockConditions" -#[derive(Debug, Clone)] -pub struct PolicyTypeUnlockConditions(UnlockCondition); - -#[derive(Debug, Clone)] -pub struct UnlockCondition { - pubkeys: Vec, - timelock: u64, - sigs_required: u64, -} - -impl UnlockCondition { - pub fn new(pubkeys: Vec, timelock: u64, sigs_required: u64) -> Self { - // TODO check go implementation to see if there should be limitations or checks imposed here - UnlockCondition { - pubkeys, - timelock, - sigs_required, - } - } - - pub fn unlock_hash(&self) -> H256 { - // almost all UnlockConditions are standard, so optimize for that case - if self.timelock == 0 && self.pubkeys.len() == 1 && self.sigs_required == 1 { - return standard_unlock_hash(&self.pubkeys[0]); - } - - let mut accumulator = Accumulator::default(); - - accumulator.add_leaf(timelock_leaf(self.timelock)); - - for pubkey in &self.pubkeys { - accumulator.add_leaf(public_key_leaf(pubkey)); - } - - accumulator.add_leaf(sigs_required_leaf(self.sigs_required)); - accumulator.root() - } - - pub fn address(&self) -> Address { Address(self.unlock_hash()) } -} - -#[test] -fn test_unlock_condition_unlock_hash_standard() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let unlock_condition = UnlockCondition::new(vec![pubkey], 0, 1); - - let hash = unlock_condition.unlock_hash(); - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(hash, expected); - - let hash = standard_unlock_hash(&pubkey); - assert_eq!(hash, expected); -} - -#[test] -fn test_unlock_condition_unlock_hash_2of2_multisig() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey2 = PublicKey::from_bytes( - &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let unlock_condition = UnlockCondition::new(vec![pubkey, pubkey2], 0, 2); - - let hash = unlock_condition.unlock_hash(); - let expected = H256::from("1e94357817d236167e54970a8c08bbd41b37bfceeeb52f6c1ce6dd01d50ea1e7"); - assert_eq!(hash, expected); -} - -#[test] -fn test_unlock_condition_unlock_hash_1of2_multisig() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey2 = PublicKey::from_bytes( - &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let unlock_condition = UnlockCondition::new(vec![pubkey, pubkey2], 0, 1); - - let hash = unlock_condition.unlock_hash(); - let expected = H256::from("d7f84e3423da09d111a17f64290c8d05e1cbe4cab2b6bed49e3a4d2f659f0585"); - assert_eq!(hash, expected); -} - -#[test] -fn test_spend_policy_encode_above() { - let policy = SpendPolicy::above(1); - - let hash = policy.encode().hash(); - let expected = H256::from("bebf6cbdfb440a92e3e5d832ac30fe5d226ff6b352ed3a9398b7d35f086a8ab6"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:188b997bb99dee13e95f92c3ea150bd76b3ec72e5ba57b0d57439a1a6e2865e9b25ea5d1825e").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_after() { - let policy = SpendPolicy::after(1); - - let hash = policy.encode().hash(); - let expected = H256::from("07b0f28eafd87a082ad11dc4724e1c491821260821a30bec68254444f97d9311"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:60c74e0ce5cede0f13f83b0132cb195c995bc7688c9fac34bbf2b14e14394b8bbe2991bc017f").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_pubkey() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let policy = SpendPolicy::PublicKey(pubkey); - - let hash = policy.encode().hash(); - let expected = H256::from("4355c8f80f6e5a98b70c9c2f9a22f17747989b4744783c90439b2b034f698bfe"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:55a7793237722c6df8222fd512063cb74228085ef1805c5184713648c159b919ac792fbad0e1").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_hash() { - let hash = H256::from("0102030000000000000000000000000000000000000000000000000000000000"); - let policy = SpendPolicy::Hash(hash); - - let encoded = policy.encode(); - let hash = encoded.hash(); - let expected = H256::from("9938967aefa6cbecc1f1620d2df5170d6811d4b2f47a879b621c1099a3b0628a"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:a4d5a06d8d3c2e45aa26627858ce8e881505ae3c9d122a1d282c7824163751936cffb347e435").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_threshold() { - let policy = SpendPolicy::Threshold(PolicyTypeThreshold { - n: 1, - of: vec![SpendPolicy::above(1), SpendPolicy::after(1)], - }); - - let encoded = policy.encode(); - let hash = encoded.hash(); - let expected = H256::from("7d792df6cd0b5e0f795287b3bf4087bbcc4c1bd0c52880a552cdda3e5e33d802"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:4179b53aba165e46e4c85b3c8766bb758fb6f0bfa5721550b81981a3ec38efc460557dc1ded4").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_unlock_condition() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let unlock_condition = UnlockCondition::new(vec![pubkey], 0, 1); - - let sub_policy = SpendPolicy::UnlockConditions(PolicyTypeUnlockConditions(unlock_condition)); - let base_address = sub_policy.address(); - let expected = - Address::from_str("addr:72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515dd64b9a56043a").unwrap(); - assert_eq!(base_address, expected); - - let policy = SpendPolicy::Threshold(PolicyTypeThreshold { - n: 1, - of: vec![sub_policy], - }); - let address = policy.address(); - let expected = - Address::from_str("addr:1498a58c843ce66740e52421632d67a0f6991ea96db1fc97c29e46f89ae56e3534078876331d").unwrap(); - assert_eq!(address, expected); -} diff --git a/mm2src/coins/sia.rs b/mm2src/coins/siacoin.rs similarity index 80% rename from mm2src/coins/sia.rs rename to mm2src/coins/siacoin.rs index 446a507070..bc57aaaf10 100644 --- a/mm2src/coins/sia.rs +++ b/mm2src/coins/siacoin.rs @@ -1,5 +1,5 @@ -use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, RawTransactionRequest, SwapOps, - TradeFee, TransactionEnum, TransactionFut}; +use super::{BalanceError, CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, + RawTransactionRequest, SwapOps, TradeFee, TransactionEnum, TransactionFut}; use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, DexFee, FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, @@ -14,25 +14,22 @@ use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPay WithdrawRequest}; use async_trait::async_trait; use common::executor::AbortedError; +pub use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signature}; use futures::{FutureExt, TryFutureExt}; use futures01::Future; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; -use mm2_number::{BigDecimal, MmNumber}; +use mm2_number::{BigDecimal, BigInt, MmNumber}; use rpc::v1::types::Bytes as BytesJson; use serde_json::Value as Json; use std::ops::Deref; use std::sync::Arc; -use url::Url; -pub mod address; -use address::v1_standard_address_from_pubkey; -pub mod blake2b_internal; -pub mod encoding; -pub mod http_client; -use http_client::{SiaApiClient, SiaApiClientError}; -pub mod spend_policy; +use sia_rust::http_client::{SiaApiClient, SiaApiClientError, SiaHttpConf}; +use sia_rust::spend_policy::SpendPolicy; + +pub mod sia_hd_wallet; #[derive(Clone)] pub struct SiaCoin(SiaArc); @@ -54,12 +51,6 @@ pub struct SiaCoinConf { pub foo: u32, } -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct SiaHttpConf { - pub url: Url, - pub auth: String, -} - // TODO see https://github.com/KomodoPlatform/komodo-defi-framework/pull/2086#discussion_r1521660384 // for additional fields needed #[derive(Clone, Debug, Deserialize, Serialize)] @@ -93,7 +84,7 @@ impl<'a> SiaConfBuilder<'a> { pub struct SiaCoinFields { /// SIA coin config pub conf: SiaCoinConf, - pub priv_key_policy: PrivKeyPolicy, + pub priv_key_policy: PrivKeyPolicy, /// HTTP(s) client pub http_client: SiaApiClient, } @@ -118,7 +109,7 @@ pub struct SiaCoinBuilder<'a> { ctx: &'a MmArc, ticker: &'a str, conf: &'a Json, - key_pair: ed25519_dalek::Keypair, + key_pair: Keypair, params: &'a SiaCoinActivationParams, } @@ -127,7 +118,7 @@ impl<'a> SiaCoinBuilder<'a> { ctx: &'a MmArc, ticker: &'a str, conf: &'a Json, - key_pair: ed25519_dalek::Keypair, + key_pair: Keypair, params: &'a SiaCoinActivationParams, ) -> Self { SiaCoinBuilder { @@ -140,15 +131,22 @@ impl<'a> SiaCoinBuilder<'a> { } } -fn generate_keypair_from_slice(priv_key: &[u8]) -> Result { - let secret_key = ed25519_dalek::SecretKey::from_bytes(priv_key).map_err(SiaCoinBuildError::EllipticCurveError)?; - let public_key = ed25519_dalek::PublicKey::from(&secret_key); - Ok(ed25519_dalek::Keypair { +fn generate_keypair_from_slice(priv_key: &[u8]) -> Result { + let secret_key = SecretKey::from_bytes(priv_key).map_err(SiaCoinBuildError::EllipticCurveError)?; + let public_key = PublicKey::from(&secret_key); + Ok(Keypair { secret: secret_key, public: public_key, }) } +/// Convert hastings amount to siacoin amount +fn siacoin_from_hastings(hastings: u128) -> BigDecimal { + let hastings = BigInt::from(hastings); + let decimals = BigInt::from(10u128.pow(24)); + BigDecimal::from(hastings) / BigDecimal::from(decimals) +} + impl From for SiaCoinBuildError { fn from(e: SiaConfError) -> Self { SiaCoinBuildError::ConfError(e) } } @@ -174,8 +172,9 @@ impl<'a> SiaCoinBuilder<'a> { let conf = SiaConfBuilder::new(self.conf, self.ticker()).build()?; let sia_fields = SiaCoinFields { conf, - http_client: SiaApiClient::new(self.ticker(), self.params.http_conf.clone()) - .map_err(SiaCoinBuildError::ClientError)?, + http_client: SiaApiClient::new(self.params.http_conf.clone()) + .map_err(SiaCoinBuildError::ClientError) + .await?, priv_key_policy: PrivKeyPolicy::Iguana(self.key_pair), }; let sia_arc = SiaArc::new(sia_fields); @@ -306,9 +305,15 @@ impl MarketCoinOps for SiaCoin { ) .into()); }, + #[cfg(target_arch = "wasm32")] + PrivKeyPolicy::Metamask(_) => { + return Err(MyAddressError::UnexpectedDerivationMethod( + "Metamask not supported. Must use iguana seed.".to_string(), + ) + .into()); + }, }; - - let address = v1_standard_address_from_pubkey(&key_pair.public); + let address = SpendPolicy::PublicKey(key_pair.public).address(); Ok(address.to_string()) } @@ -323,14 +328,30 @@ impl MarketCoinOps for SiaCoin { } fn my_balance(&self) -> BalanceFut { + let coin = self.clone(); let fut = async move { + let my_address = match &coin.0.priv_key_policy { + PrivKeyPolicy::Iguana(key_pair) => SpendPolicy::PublicKey(key_pair.public).address(), + _ => { + return MmError::err(BalanceError::UnexpectedDerivationMethod( + UnexpectedDerivationMethod::ExpectedSingleAddress, + )) + }, + }; + let balance = coin + .0 + .http_client + .address_balance(my_address) + .await + .map_to_mm(|e| BalanceError::Transport(e.to_string()))?; Ok(CoinBalance { - spendable: BigDecimal::default(), - unspendable: BigDecimal::default(), + spendable: siacoin_from_hastings(balance.siacoins.to_u128()), + unspendable: siacoin_from_hastings(balance.immature_siacoins.to_u128()), }) }; Box::new(fut.boxed().compat()) } + fn base_coin_balance(&self) -> BalanceFut { unimplemented!() } fn platform_ticker(&self) -> &str { "FOO" } // TODO Alright @@ -349,7 +370,7 @@ impl MarketCoinOps for SiaCoin { unimplemented!() } - fn wait_for_htlc_tx_spend(&self, _args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { unimplemented!() } + async fn wait_for_htlc_tx_spend(&self, _args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { unimplemented!() } fn tx_enum_from_bytes(&self, _bytes: &[u8]) -> Result> { MmError::err(TxMarshalingErr::NotSupported( @@ -360,7 +381,7 @@ impl MarketCoinOps for SiaCoin { fn current_block(&self) -> Box + Send> { let http_client = self.0.http_client.clone(); // Clone the client - let height_fut = async move { http_client.get_height().await.map_err(|e| e.to_string()) } + let height_fut = async move { http_client.current_height().await.map_err(|e| e.to_string()) } .boxed() // Make the future 'static by boxing .compat(); // Convert to a futures 0.1-compatible future @@ -378,13 +399,23 @@ impl MarketCoinOps for SiaCoin { #[async_trait] impl SwapOps for SiaCoin { - fn send_taker_fee(&self, _fee_addr: &[u8], _dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { + async fn send_taker_fee( + &self, + _fee_addr: &[u8], + _dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { unimplemented!() } - fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } + async fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + unimplemented!() + } - fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } + async fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + unimplemented!() + } async fn send_maker_spends_taker_payment( &self, @@ -414,7 +445,9 @@ impl SwapOps for SiaCoin { unimplemented!() } - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } + async fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { + unimplemented!() + } async fn validate_maker_payment(&self, _input: ValidatePaymentInput) -> ValidatePaymentResult<()> { unimplemented!() @@ -424,10 +457,10 @@ impl SwapOps for SiaCoin { unimplemented!() } - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - _if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { + _if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { unimplemented!() } @@ -473,9 +506,7 @@ impl SwapOps for SiaCoin { fn derive_htlc_pubkey(&self, _swap_unique_data: &[u8]) -> Vec { unimplemented!() } - fn can_refund_htlc(&self, _locktime: u64) -> Box + Send + '_> { - unimplemented!() - } + async fn can_refund_htlc(&self, _locktime: u64) -> Result { unimplemented!() } fn validate_other_pubkey(&self, _raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { unimplemented!() } @@ -596,3 +627,29 @@ impl WatcherOps for SiaCoin { unimplemented!() } } + +#[cfg(test)] +mod tests { + use super::*; + use mm2_number::BigDecimal; + use std::str::FromStr; + + #[test] + fn test_siacoin_from_hastings() { + let hastings = u128::MAX; + let siacoin = siacoin_from_hastings(hastings); + assert_eq!( + siacoin, + BigDecimal::from_str("340282366920938.463463374607431768211455").unwrap() + ); + + let hastings = 0; + let siacoin = siacoin_from_hastings(hastings); + assert_eq!(siacoin, BigDecimal::from_str("0").unwrap()); + + // Total supply of Siacoin + let hastings = 57769875000000000000000000000000000; + let siacoin = siacoin_from_hastings(hastings); + assert_eq!(siacoin, BigDecimal::from_str("57769875000").unwrap()); + } +} diff --git a/mm2src/coins/siacoin/sia_hd_wallet.rs b/mm2src/coins/siacoin/sia_hd_wallet.rs new file mode 100644 index 0000000000..4c6a288ef5 --- /dev/null +++ b/mm2src/coins/siacoin/sia_hd_wallet.rs @@ -0,0 +1,44 @@ +use crate::hd_wallet::{HDAccount, HDAddress, HDWallet}; +use bip32::{ExtendedPublicKey, PrivateKeyBytes, PublicKey as bip32PublicKey, PublicKeyBytes, Result as bip32Result}; +use sia_rust::types::Address; +use sia_rust::PublicKey; + +pub struct SiaPublicKey(pub PublicKey); + +pub type SiaHDAddress = HDAddress; +pub type SiaHDAccount = HDAccount; +pub type SiaHDWallet = HDWallet; +pub type Ed25519ExtendedPublicKey = ExtendedPublicKey; + +impl bip32PublicKey for SiaPublicKey { + fn from_bytes(_bytes: PublicKeyBytes) -> bip32Result { + todo!() + //Ok(secp256k1_ffi::PublicKey::from_slice(&bytes)?) + } + + fn to_bytes(&self) -> PublicKeyBytes { + todo!() + // self.serialize() + } + + fn derive_child(&self, _other: PrivateKeyBytes) -> bip32Result { + todo!() + // use secp256k1_ffi::{Secp256k1, VerifyOnly}; + // let engine = Secp256k1::::verification_only(); + + // let mut child_key = *self; + // child_key + // .add_exp_assign(&engine, &other) + // .map_err(|_| Error::Crypto)?; + + // Ok(child_key) + } +} + +// coin type 1991 +// path component 0x800007c7 + +#[test] +fn test_something() { + println!("This is a test"); +} diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs deleted file mode 100644 index 2a454fd90c..0000000000 --- a/mm2src/coins/solana.rs +++ /dev/null @@ -1,808 +0,0 @@ -use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, SwapOps, TradeFee, TransactionEnum, WatcherOps}; -use crate::coin_errors::{MyAddressError, ValidatePaymentResult}; -use crate::hd_wallet::HDPathAccountToAddressId; -use crate::solana::solana_common::{lamports_to_sol, PrepareTransferData, SufficientBalanceError}; -use crate::solana::spl::SplTokenInfo; -use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, DexFee, - FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, - PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, - PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, - RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, SignatureResult, - SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TransactionData, TransactionDetails, TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; -use async_trait::async_trait; -use base58::ToBase58; -use bincode::{deserialize, serialize}; -use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError}; -use common::{async_blocking, now_sec}; -use crypto::HDPathToCoin; -use derive_more::Display; -use futures::{FutureExt, TryFutureExt}; -use futures01::Future; -use keys::KeyPair; -use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::prelude::*; -use mm2_number::{BigDecimal, MmNumber}; -use rpc::v1::types::Bytes as BytesJson; -use serde_json::{self as json, Value as Json}; -use solana_client::rpc_request::TokenAccountsFilter; -use solana_client::{client_error::{ClientError, ClientErrorKind}, - rpc_client::RpcClient}; -use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel}; -use solana_sdk::program_error::ProgramError; -use solana_sdk::pubkey::ParsePubkeyError; -use solana_sdk::transaction::Transaction; -use solana_sdk::{pubkey::Pubkey, - signature::{Keypair, Signer}}; -use std::collections::HashMap; -use std::str::FromStr; -use std::sync::Mutex; -use std::{convert::TryFrom, fmt::Debug, ops::Deref, sync::Arc}; - -pub mod solana_common; -mod solana_decode_tx_helpers; -pub mod spl; - -#[cfg(test)] mod solana_common_tests; -#[cfg(test)] mod solana_tests; -#[cfg(test)] mod spl_tests; - -pub const SOLANA_DEFAULT_DECIMALS: u64 = 9; -pub const LAMPORTS_DUMMY_AMOUNT: u64 = 10; - -#[async_trait] -pub trait SolanaCommonOps { - fn rpc(&self) -> &RpcClient; - - fn is_token(&self) -> bool; - - async fn check_balance_and_prepare_transfer( - &self, - max: bool, - amount: BigDecimal, - fees: u64, - ) -> Result>; -} - -impl From for BalanceError { - fn from(e: ClientError) -> Self { - match e.kind { - ClientErrorKind::Io(e) => BalanceError::Transport(e.to_string()), - ClientErrorKind::Reqwest(e) => BalanceError::Transport(e.to_string()), - ClientErrorKind::RpcError(e) => BalanceError::Transport(format!("{:?}", e)), - ClientErrorKind::SerdeJson(e) => BalanceError::InvalidResponse(e.to_string()), - ClientErrorKind::Custom(e) => BalanceError::Internal(e), - ClientErrorKind::SigningError(_) - | ClientErrorKind::TransactionError(_) - | ClientErrorKind::FaucetError(_) => BalanceError::Internal("not_reacheable".to_string()), - } - } -} - -impl From for BalanceError { - fn from(e: ParsePubkeyError) -> Self { BalanceError::Internal(format!("{:?}", e)) } -} - -impl From for WithdrawError { - fn from(e: ClientError) -> Self { - match e.kind { - ClientErrorKind::Io(e) => WithdrawError::Transport(e.to_string()), - ClientErrorKind::Reqwest(e) => WithdrawError::Transport(e.to_string()), - ClientErrorKind::RpcError(e) => WithdrawError::Transport(format!("{:?}", e)), - ClientErrorKind::SerdeJson(e) => WithdrawError::InternalError(e.to_string()), - ClientErrorKind::Custom(e) => WithdrawError::InternalError(e), - ClientErrorKind::SigningError(_) - | ClientErrorKind::TransactionError(_) - | ClientErrorKind::FaucetError(_) => WithdrawError::InternalError("not_reacheable".to_string()), - } - } -} - -impl From for WithdrawError { - fn from(e: ParsePubkeyError) -> Self { WithdrawError::InvalidAddress(format!("{:?}", e)) } -} - -impl From for WithdrawError { - fn from(e: ProgramError) -> Self { WithdrawError::InternalError(format!("{:?}", e)) } -} - -#[derive(Debug)] -pub enum AccountError { - NotFundedError(String), - ParsePubKeyError(String), - ClientError(ClientErrorKind), -} - -impl From for AccountError { - fn from(e: ClientError) -> Self { AccountError::ClientError(e.kind) } -} - -impl From for AccountError { - fn from(e: ParsePubkeyError) -> Self { AccountError::ParsePubKeyError(format!("{:?}", e)) } -} - -impl From for WithdrawError { - fn from(e: AccountError) -> Self { - match e { - AccountError::NotFundedError(_) => WithdrawError::ZeroBalanceToWithdrawMax, - AccountError::ParsePubKeyError(err) => WithdrawError::InternalError(err), - AccountError::ClientError(e) => WithdrawError::Transport(format!("{:?}", e)), - } - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct SolanaActivationParams { - confirmation_commitment: CommitmentLevel, - client_url: String, - #[serde(default)] - path_to_address: HDPathAccountToAddressId, -} - -#[derive(Debug, Display)] -pub enum SolanaFromLegacyReqErr { - InvalidCommitmentLevel(String), - InvalidClientParsing(json::Error), - ClientNoAvailableNodes(String), -} - -#[derive(Debug, Display)] -pub enum KeyPairCreationError { - #[display(fmt = "Signature error: {}", _0)] - SignatureError(ed25519_dalek::SignatureError), - #[display(fmt = "KeyPairFromSeed error: {}", _0)] - KeyPairFromSeed(String), -} - -impl From for KeyPairCreationError { - fn from(e: ed25519_dalek::SignatureError) -> Self { KeyPairCreationError::SignatureError(e) } -} - -fn generate_keypair_from_slice(priv_key: &[u8]) -> Result> { - let secret_key = ed25519_dalek::SecretKey::from_bytes(priv_key)?; - let public_key = ed25519_dalek::PublicKey::from(&secret_key); - let key_pair = ed25519_dalek::Keypair { - secret: secret_key, - public: public_key, - }; - solana_sdk::signature::keypair_from_seed(key_pair.to_bytes().as_ref()) - .map_to_mm(|e| KeyPairCreationError::KeyPairFromSeed(e.to_string())) -} - -pub async fn solana_coin_with_policy( - ctx: &MmArc, - ticker: &str, - conf: &Json, - params: SolanaActivationParams, - priv_key_policy: PrivKeyBuildPolicy, -) -> Result { - let client = RpcClient::new_with_commitment(params.client_url.clone(), CommitmentConfig { - commitment: params.confirmation_commitment, - }); - let decimals = conf["decimals"].as_u64().unwrap_or(SOLANA_DEFAULT_DECIMALS) as u8; - - let priv_key = match priv_key_policy { - PrivKeyBuildPolicy::IguanaPrivKey(priv_key) => priv_key, - PrivKeyBuildPolicy::GlobalHDAccount(global_hd) => { - let path_to_coin: HDPathToCoin = try_s!(json::from_value(conf["derivation_path"].clone())); - let derivation_path = try_s!(params.path_to_address.to_derivation_path(&path_to_coin)); - try_s!(global_hd.derive_secp256k1_secret(&derivation_path)) - }, - PrivKeyBuildPolicy::Trezor => return ERR!("{}", PrivKeyPolicyNotAllowed::HardwareWalletNotSupported), - }; - - let key_pair = try_s!(generate_keypair_from_slice(priv_key.as_slice())); - let my_address = key_pair.pubkey().to_string(); - let spl_tokens_infos = Arc::new(Mutex::new(HashMap::new())); - - // Create an abortable system linked to the `MmCtx` so if the context is stopped via `MmArc::stop`, - // all spawned futures related to `SolanaCoin` will be aborted as well. - let abortable_system: AbortableQueue = try_s!(ctx.abortable_system.create_subsystem()); - - let solana_coin = SolanaCoin(Arc::new(SolanaCoinImpl { - my_address, - key_pair, - ticker: ticker.to_string(), - client, - decimals, - spl_tokens_infos, - abortable_system, - })); - Ok(solana_coin) -} - -/// pImpl idiom. -pub struct SolanaCoinImpl { - ticker: String, - key_pair: Keypair, - client: RpcClient, - decimals: u8, - my_address: String, - spl_tokens_infos: Arc>>, - /// This spawner is used to spawn coin's related futures that should be aborted on coin deactivation - /// and on [`MmArc::stop`]. - pub abortable_system: AbortableQueue, -} - -#[derive(Clone)] -pub struct SolanaCoin(Arc); -impl Deref for SolanaCoin { - type Target = SolanaCoinImpl; - fn deref(&self) -> &SolanaCoinImpl { &self.0 } -} - -#[async_trait] -impl SolanaCommonOps for SolanaCoin { - fn rpc(&self) -> &RpcClient { &self.client } - - fn is_token(&self) -> bool { false } - - async fn check_balance_and_prepare_transfer( - &self, - max: bool, - amount: BigDecimal, - fees: u64, - ) -> Result> { - solana_common::check_balance_and_prepare_transfer(self, max, amount, fees).await - } -} - -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -pub struct SolanaFeeDetails { - pub amount: BigDecimal, -} - -async fn withdraw_base_coin_impl(coin: SolanaCoin, req: WithdrawRequest) -> WithdrawResult { - let (hash, fees) = coin.estimate_withdraw_fees().await?; - let res = coin - .check_balance_and_prepare_transfer(req.max, req.amount.clone(), fees) - .await?; - let to = solana_sdk::pubkey::Pubkey::try_from(&*req.to)?; - let tx = solana_sdk::system_transaction::transfer(&coin.key_pair, &to, res.lamports_to_send, hash); - let serialized_tx = serialize(&tx).map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; - let total_amount = lamports_to_sol(res.lamports_to_send); - let received_by_me = if req.to == coin.my_address { - total_amount.clone() - } else { - 0.into() - }; - let spent_by_me = &total_amount + &res.sol_required; - Ok(TransactionDetails { - tx: TransactionData::new_signed(serialized_tx.into(), tx.signatures[0].to_string()), - from: vec![coin.my_address.clone()], - to: vec![req.to], - total_amount: spent_by_me.clone(), - my_balance_change: &received_by_me - &spent_by_me, - spent_by_me, - received_by_me, - block_height: 0, - timestamp: now_sec(), - fee_details: Some( - SolanaFeeDetails { - amount: res.sol_required, - } - .into(), - ), - coin: coin.ticker.clone(), - internal_id: vec![].into(), - kmd_rewards: None, - transaction_type: TransactionType::StandardTransfer, - memo: None, - }) -} - -async fn withdraw_impl(coin: SolanaCoin, req: WithdrawRequest) -> WithdrawResult { - let validate_address_result = coin.validate_address(&req.to); - if !validate_address_result.is_valid { - return MmError::err(WithdrawError::InvalidAddress( - validate_address_result.reason.unwrap_or_else(|| "Unknown".to_string()), - )); - } - withdraw_base_coin_impl(coin, req).await -} - -impl SolanaCoin { - pub async fn estimate_withdraw_fees(&self) -> Result<(solana_sdk::hash::Hash, u64), MmError> { - let hash = async_blocking({ - let coin = self.clone(); - move || coin.rpc().get_latest_blockhash() - }) - .await?; - let to = self.key_pair.pubkey(); - - let tx = solana_sdk::system_transaction::transfer(&self.key_pair, &to, LAMPORTS_DUMMY_AMOUNT, hash); - let fees = async_blocking({ - let coin = self.clone(); - move || coin.rpc().get_fee_for_message(tx.message()) - }) - .await?; - Ok((hash, fees)) - } - - pub async fn my_balance_spl(&self, infos: SplTokenInfo) -> Result> { - let token_accounts = async_blocking({ - let coin = self.clone(); - move || { - coin.rpc().get_token_accounts_by_owner( - &coin.key_pair.pubkey(), - TokenAccountsFilter::Mint(infos.token_contract_address), - ) - } - }) - .await?; - if token_accounts.is_empty() { - return Ok(CoinBalance { - spendable: Default::default(), - unspendable: Default::default(), - }); - } - let actual_token_pubkey = - Pubkey::from_str(&token_accounts[0].pubkey).map_err(|e| BalanceError::Internal(format!("{:?}", e)))?; - let amount = async_blocking({ - let coin = self.clone(); - move || coin.rpc().get_token_account_balance(&actual_token_pubkey) - }) - .await?; - let balance = - BigDecimal::from_str(&amount.ui_amount_string).map_to_mm(|e| BalanceError::Internal(e.to_string()))?; - Ok(CoinBalance { - spendable: balance, - unspendable: Default::default(), - }) - } - - fn my_balance_impl(&self) -> BalanceFut { - let coin = self.clone(); - let fut = async_blocking(move || { - // this is blocking IO - let res = coin.rpc().get_balance(&coin.key_pair.pubkey())?; - Ok(lamports_to_sol(res)) - }); - Box::new(fut.boxed().compat()) - } - - pub fn add_spl_token_info(&self, ticker: String, info: SplTokenInfo) { - self.spl_tokens_infos.lock().unwrap().insert(ticker, info); - } - - /// WARNING - /// Be very careful using this function since it returns dereferenced clone - /// of value behind the MutexGuard and makes it non-thread-safe. - pub fn get_spl_tokens_infos(&self) -> HashMap { - let guard = self.spl_tokens_infos.lock().unwrap(); - (*guard).clone() - } -} - -#[async_trait] -impl MarketCoinOps for SolanaCoin { - fn ticker(&self) -> &str { &self.ticker } - - fn my_address(&self) -> MmResult { Ok(self.my_address.clone()) } - - async fn get_public_key(&self) -> Result> { unimplemented!() } - - fn sign_message_hash(&self, _message: &str) -> Option<[u8; 32]> { unimplemented!() } - - fn sign_message(&self, message: &str) -> SignatureResult { solana_common::sign_message(self, message) } - - fn verify_message(&self, signature: &str, message: &str, pubkey_bs58: &str) -> VerificationResult { - solana_common::verify_message(self, signature, message, pubkey_bs58) - } - - fn my_balance(&self) -> BalanceFut { - let decimals = self.decimals as u64; - let fut = self.my_balance_impl().and_then(move |result| { - Ok(CoinBalance { - spendable: result.with_prec(decimals), - unspendable: 0.into(), - }) - }); - Box::new(fut) - } - - fn base_coin_balance(&self) -> BalanceFut { - let decimals = self.decimals as u64; - let fut = self - .my_balance_impl() - .and_then(move |result| Ok(result.with_prec(decimals))); - Box::new(fut) - } - - fn platform_ticker(&self) -> &str { self.ticker() } - - fn send_raw_tx(&self, tx: &str) -> Box + Send> { - let coin = self.clone(); - let tx = tx.to_owned(); - let fut = async_blocking(move || { - let bytes = hex::decode(tx).map_to_mm(|e| e).map_err(|e| format!("{:?}", e))?; - let tx: Transaction = deserialize(bytes.as_slice()) - .map_to_mm(|e| e) - .map_err(|e| format!("{:?}", e))?; - // this is blocking IO - let signature = coin.rpc().send_transaction(&tx).map_err(|e| format!("{:?}", e))?; - Ok(signature.to_string()) - }); - Box::new(fut.boxed().compat()) - } - - fn send_raw_tx_bytes(&self, tx: &[u8]) -> Box + Send> { - let coin = self.clone(); - let tx = tx.to_owned(); - let fut = async_blocking(move || { - let tx = try_s!(deserialize(tx.as_slice())); - // this is blocking IO - let signature = coin.rpc().send_transaction(&tx).map_err(|e| format!("{:?}", e))?; - Ok(signature.to_string()) - }); - Box::new(fut.boxed().compat()) - } - - #[inline(always)] - async fn sign_raw_tx(&self, _args: &SignRawTransactionRequest) -> RawTransactionResult { - MmError::err(RawTransactionError::NotImplemented { - coin: self.ticker().to_string(), - }) - } - - fn wait_for_confirmations(&self, _input: ConfirmPaymentInput) -> Box + Send> { - unimplemented!() - } - - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { unimplemented!() } - - fn tx_enum_from_bytes(&self, _bytes: &[u8]) -> Result> { - MmError::err(TxMarshalingErr::NotSupported( - "tx_enum_from_bytes is not supported for Solana yet.".to_string(), - )) - } - - fn current_block(&self) -> Box + Send> { - let coin = self.clone(); - let fut = async_blocking(move || coin.rpc().get_block_height().map_err(|e| format!("{:?}", e))); - Box::new(fut.boxed().compat()) - } - - fn display_priv_key(&self) -> Result { Ok(self.key_pair.secret().to_bytes()[..].to_base58()) } - - fn min_tx_amount(&self) -> BigDecimal { BigDecimal::from(0) } - - fn min_trading_vol(&self) -> MmNumber { MmNumber::from("0.00777") } - - fn is_trezor(&self) -> bool { unimplemented!() } -} - -#[async_trait] -impl SwapOps for SolanaCoin { - fn send_taker_fee(&self, _fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { - unimplemented!() - } - - fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - - fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - - async fn send_maker_spends_taker_payment( - &self, - _maker_spends_payment_args: SpendPaymentArgs<'_>, - ) -> TransactionResult { - unimplemented!() - } - - async fn send_taker_spends_maker_payment( - &self, - _taker_spends_payment_args: SpendPaymentArgs<'_>, - ) -> TransactionResult { - unimplemented!() - } - - async fn send_taker_refunds_payment( - &self, - _taker_refunds_payment_args: RefundPaymentArgs<'_>, - ) -> TransactionResult { - unimplemented!() - } - - async fn send_maker_refunds_payment( - &self, - _maker_refunds_payment_args: RefundPaymentArgs<'_>, - ) -> TransactionResult { - unimplemented!() - } - - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } - - async fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { - unimplemented!() - } - - async fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { - unimplemented!() - } - - fn check_if_my_payment_sent( - &self, - _if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { - unimplemented!() - } - - async fn search_for_swap_tx_spend_my( - &self, - _: SearchForSwapTxSpendInput<'_>, - ) -> Result, String> { - unimplemented!() - } - - async fn search_for_swap_tx_spend_other( - &self, - _: SearchForSwapTxSpendInput<'_>, - ) -> Result, String> { - unimplemented!() - } - - fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result> { - unimplemented!(); - } - - async fn extract_secret( - &self, - secret_hash: &[u8], - spend_tx: &[u8], - watcher_reward: bool, - ) -> Result, String> { - unimplemented!() - } - - fn is_auto_refundable(&self) -> bool { false } - - async fn wait_for_htlc_refund(&self, _tx: &[u8], _locktime: u64) -> RefundResult<()> { - MmError::err(RefundError::Internal( - "wait_for_htlc_refund is not supported for this coin!".into(), - )) - } - - fn negotiate_swap_contract_addr( - &self, - _other_side_address: Option<&[u8]>, - ) -> Result, MmError> { - unimplemented!() - } - - #[inline] - fn derive_htlc_key_pair(&self, _swap_unique_data: &[u8]) -> KeyPair { todo!() } - - #[inline] - fn derive_htlc_pubkey(&self, swap_unique_data: &[u8]) -> Vec { - self.derive_htlc_key_pair(swap_unique_data).public_slice().to_vec() - } - - fn validate_other_pubkey(&self, _raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { unimplemented!() } - - async fn maker_payment_instructions( - &self, - _args: PaymentInstructionArgs<'_>, - ) -> Result>, MmError> { - unimplemented!() - } - - async fn taker_payment_instructions( - &self, - _args: PaymentInstructionArgs<'_>, - ) -> Result>, MmError> { - unimplemented!() - } - - fn validate_maker_payment_instructions( - &self, - _instructions: &[u8], - _args: PaymentInstructionArgs<'_>, - ) -> Result> { - unimplemented!() - } - - fn validate_taker_payment_instructions( - &self, - _instructions: &[u8], - _args: PaymentInstructionArgs<'_>, - ) -> Result> { - unimplemented!() - } -} - -#[async_trait] -impl TakerSwapMakerCoin for SolanaCoin { - async fn on_taker_payment_refund_start(&self, _maker_payment: &[u8]) -> RefundResult<()> { Ok(()) } - - async fn on_taker_payment_refund_success(&self, _maker_payment: &[u8]) -> RefundResult<()> { Ok(()) } -} - -#[async_trait] -impl MakerSwapTakerCoin for SolanaCoin { - async fn on_maker_payment_refund_start(&self, _taker_payment: &[u8]) -> RefundResult<()> { Ok(()) } - - async fn on_maker_payment_refund_success(&self, _taker_payment: &[u8]) -> RefundResult<()> { Ok(()) } -} - -#[async_trait] -impl WatcherOps for SolanaCoin { - fn create_maker_payment_spend_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u64, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - - fn send_maker_payment_spend_preimage(&self, _input: SendMakerPaymentSpendPreimageInput) -> TransactionFut { - unimplemented!(); - } - - fn create_taker_payment_refund_preimage( - &self, - _taker_payment_tx: &[u8], - _time_lock: u64, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_contract_address: &Option, - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - - fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - unimplemented!(); - } - - fn watcher_validate_taker_fee(&self, input: WatcherValidateTakerFeeInput) -> ValidatePaymentFut<()> { - unimplemented!(); - } - - fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { - unimplemented!(); - } - - fn taker_validates_payment_spend_or_refund(&self, _input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { - unimplemented!() - } - - async fn watcher_search_for_swap_tx_spend( - &self, - input: WatcherSearchForSwapTxSpendInput<'_>, - ) -> Result, String> { - unimplemented!(); - } - - async fn get_taker_watcher_reward( - &self, - other_coin: &MmCoinEnum, - coin_amount: Option, - other_coin_amount: Option, - reward_amount: Option, - wait_until: u64, - ) -> Result> { - unimplemented!(); - } - - async fn get_maker_watcher_reward( - &self, - other_coin: &MmCoinEnum, - reward_amount: Option, - wait_until: u64, - ) -> Result, MmError> { - unimplemented!(); - } -} - -#[async_trait] -impl MmCoin for SolanaCoin { - fn is_asset_chain(&self) -> bool { false } - - fn spawner(&self) -> CoinFutSpawner { CoinFutSpawner::new(&self.abortable_system) } - - fn withdraw(&self, req: WithdrawRequest) -> WithdrawFut { - Box::new(Box::pin(withdraw_impl(self.clone(), req)).compat()) - } - - fn get_raw_transaction(&self, _req: RawTransactionRequest) -> RawTransactionFut { unimplemented!() } - - fn get_tx_hex_by_hash(&self, tx_hash: Vec) -> RawTransactionFut { unimplemented!() } - - fn decimals(&self) -> u8 { self.decimals } - - fn convert_to_address(&self, _from: &str, _to_address_format: Json) -> Result { unimplemented!() } - - fn validate_address(&self, address: &str) -> ValidateAddressResult { - if address.len() != 44 { - return ValidateAddressResult { - is_valid: false, - reason: Some("Invalid address length".to_string()), - }; - } - let result = Pubkey::try_from(address); - match result { - Ok(pubkey) => { - if pubkey.is_on_curve() { - ValidateAddressResult { - is_valid: true, - reason: None, - } - } else { - ValidateAddressResult { - is_valid: false, - reason: Some("not_on_curve".to_string()), - } - } - }, - Err(err) => ValidateAddressResult { - is_valid: false, - reason: Some(format!("{:?}", err)), - }, - } - } - - fn process_history_loop(&self, _ctx: MmArc) -> Box + Send> { unimplemented!() } - - fn history_sync_status(&self) -> HistorySyncState { unimplemented!() } - - /// Get fee to be paid per 1 swap transaction - fn get_trade_fee(&self) -> Box + Send> { unimplemented!() } - - async fn get_sender_trade_fee( - &self, - _value: TradePreimageValue, - _stage: FeeApproxStage, - _include_refund_fee: bool, - ) -> TradePreimageResult { - unimplemented!() - } - - fn get_receiver_trade_fee(&self, _stage: FeeApproxStage) -> TradePreimageFut { unimplemented!() } - - async fn get_fee_to_send_taker_fee( - &self, - _dex_fee_amount: DexFee, - _stage: FeeApproxStage, - ) -> TradePreimageResult { - unimplemented!() - } - - fn required_confirmations(&self) -> u64 { 1 } - - fn requires_notarization(&self) -> bool { false } - - fn set_required_confirmations(&self, _confirmations: u64) { unimplemented!() } - - fn set_requires_notarization(&self, _requires_nota: bool) { unimplemented!() } - - fn swap_contract_address(&self) -> Option { unimplemented!() } - - fn fallback_swap_contract(&self) -> Option { unimplemented!() } - - fn mature_confirmations(&self) -> Option { None } - - fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { Vec::new() } - - fn is_coin_protocol_supported( - &self, - _info: &Option>, - _amount_to_send: Option, - _locktime: u64, - _is_maker: bool, - ) -> bool { - true - } - - fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.abortable_system) } - - fn on_token_deactivated(&self, _ticker: &str) {} -} diff --git a/mm2src/coins/solana/solana_common.rs b/mm2src/coins/solana/solana_common.rs deleted file mode 100644 index 3f74c1caff..0000000000 --- a/mm2src/coins/solana/solana_common.rs +++ /dev/null @@ -1,181 +0,0 @@ -use crate::solana::SolanaCommonOps; -use crate::{BalanceError, MarketCoinOps, NumConversError, SignatureError, SignatureResult, SolanaCoin, - UnexpectedDerivationMethod, VerificationError, VerificationResult, WithdrawError}; -use base58::FromBase58; -use derive_more::Display; -use futures::compat::Future01CompatExt; -use mm2_err_handle::prelude::*; -use mm2_number::bigdecimal::{BigDecimal, ToPrimitive}; -use solana_sdk::native_token::LAMPORTS_PER_SOL; -use solana_sdk::signature::{Signature, Signer}; -use std::str::FromStr; - -#[derive(Debug, Display)] -pub enum SufficientBalanceError { - #[display( - fmt = "Not enough {} to withdraw: available {}, required at least {}", - coin, - available, - required - )] - NotSufficientBalance { - coin: String, - available: BigDecimal, - required: BigDecimal, - }, - #[display(fmt = "The amount {} is too small, required at least {}", amount, threshold)] - AmountTooLow { amount: BigDecimal, threshold: BigDecimal }, - #[display(fmt = "{}", _0)] - UnexpectedDerivationMethod(UnexpectedDerivationMethod), - #[display(fmt = "Wallet storage error: {}", _0)] - WalletStorageError(String), - #[display(fmt = "Invalid response: {}", _0)] - InvalidResponse(String), - #[display(fmt = "Transport: {}", _0)] - Transport(String), - #[display(fmt = "Internal: {}", _0)] - Internal(String), -} - -impl From for SufficientBalanceError { - fn from(e: NumConversError) -> Self { SufficientBalanceError::Internal(e.to_string()) } -} - -impl From for SufficientBalanceError { - fn from(e: BalanceError) -> Self { - match e { - BalanceError::Transport(e) => SufficientBalanceError::Transport(e), - BalanceError::InvalidResponse(e) => SufficientBalanceError::InvalidResponse(e), - BalanceError::UnexpectedDerivationMethod(e) => SufficientBalanceError::UnexpectedDerivationMethod(e), - BalanceError::Internal(e) => SufficientBalanceError::Internal(e), - BalanceError::WalletStorageError(e) => SufficientBalanceError::WalletStorageError(e), - } - } -} -impl From for WithdrawError { - fn from(e: SufficientBalanceError) -> Self { - match e { - SufficientBalanceError::NotSufficientBalance { - coin, - available, - required, - } => WithdrawError::NotSufficientBalance { - coin, - available, - required, - }, - SufficientBalanceError::UnexpectedDerivationMethod(e) => WithdrawError::from(e), - SufficientBalanceError::InvalidResponse(e) | SufficientBalanceError::Transport(e) => { - WithdrawError::Transport(e) - }, - SufficientBalanceError::Internal(e) | SufficientBalanceError::WalletStorageError(e) => { - WithdrawError::InternalError(e) - }, - SufficientBalanceError::AmountTooLow { amount, threshold } => { - WithdrawError::AmountTooLow { amount, threshold } - }, - } - } -} - -pub struct PrepareTransferData { - pub to_send: BigDecimal, - pub my_balance: BigDecimal, - pub sol_required: BigDecimal, - pub lamports_to_send: u64, -} - -pub fn lamports_to_sol(lamports: u64) -> BigDecimal { BigDecimal::from(lamports) / BigDecimal::from(LAMPORTS_PER_SOL) } - -pub fn sol_to_lamports(sol: &BigDecimal) -> Result> { - let maybe_lamports = (sol * BigDecimal::from(LAMPORTS_PER_SOL)).to_u64(); - match maybe_lamports { - None => MmError::err(NumConversError("Error when converting sol to lamports".to_string())), - Some(lamports) => Ok(lamports), - } -} - -pub fn ui_amount_to_amount(ui_amount: BigDecimal, decimals: u8) -> Result> { - let maybe_amount = (ui_amount * BigDecimal::from(10_u64.pow(decimals as u32))).to_u64(); - match maybe_amount { - None => MmError::err(NumConversError("Error when converting ui amount to amount".to_string())), - Some(amount) => Ok(amount), - } -} - -pub fn amount_to_ui_amount(amount: u64, decimals: u8) -> BigDecimal { - BigDecimal::from(amount) / BigDecimal::from(10_u64.pow(decimals as u32)) -} - -pub fn sign_message(coin: &SolanaCoin, message: &str) -> SignatureResult { - let signature = coin - .key_pair - .try_sign_message(message.as_bytes()) - .map_err(|e| SignatureError::InternalError(e.to_string()))?; - Ok(signature.to_string()) -} - -pub fn verify_message( - coin: &SolanaCoin, - signature: &str, - message: &str, - pubkey_bs58: &str, -) -> VerificationResult { - let pubkey = pubkey_bs58.from_base58()?; - let signature = - Signature::from_str(signature).map_err(|e| VerificationError::SignatureDecodingError(e.to_string()))?; - let is_valid = signature.verify(&pubkey, message.as_bytes()); - Ok(is_valid) -} - -pub async fn check_balance_and_prepare_transfer( - coin: &T, - max: bool, - amount: BigDecimal, - fees: u64, -) -> Result> -where - T: SolanaCommonOps + MarketCoinOps, -{ - let base_balance = coin.base_coin_balance().compat().await?; - let sol_required = lamports_to_sol(fees); - if base_balance < sol_required { - return MmError::err(SufficientBalanceError::NotSufficientBalance { - coin: coin.platform_ticker().to_string(), - available: base_balance.clone(), - required: sol_required.clone(), - }); - } - - let my_balance = coin.my_balance().compat().await?.spendable; - let to_send = if max { my_balance.clone() } else { amount.clone() }; - let to_check = if max || coin.is_token() { - to_send.clone() - } else { - &to_send + &sol_required - }; - if to_check > my_balance { - return MmError::err(SufficientBalanceError::NotSufficientBalance { - coin: coin.ticker().to_string(), - available: my_balance, - required: to_check, - }); - } - - let lamports_to_send = if !coin.is_token() { - if max { - sol_to_lamports(&my_balance)? - sol_to_lamports(&sol_required)? - } else { - sol_to_lamports(&amount)? - } - } else { - 0_u64 - }; - - Ok(PrepareTransferData { - to_send, - my_balance, - sol_required, - lamports_to_send, - }) -} diff --git a/mm2src/coins/solana/solana_common_tests.rs b/mm2src/coins/solana/solana_common_tests.rs deleted file mode 100644 index 7706cc8abf..0000000000 --- a/mm2src/coins/solana/solana_common_tests.rs +++ /dev/null @@ -1,96 +0,0 @@ -use super::*; -use crate::solana::spl::{SplToken, SplTokenFields}; -use crypto::privkey::{bip39_seed_from_passphrase, key_pair_from_seed}; -use ed25519_dalek_bip32::{DerivationPath, ExtendedSecretKey}; -use mm2_core::mm_ctx::MmCtxBuilder; -use solana_client::rpc_client::RpcClient; -use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel}; -use std::str::FromStr; - -pub enum SolanaNet { - //Mainnet, - Testnet, - Devnet, -} - -pub fn solana_net_to_url(net_type: SolanaNet) -> String { - match net_type { - //SolanaNet::Mainnet => "https://api.mainnet-beta.solana.com".to_string(), - SolanaNet::Testnet => "https://api.testnet.solana.com/".to_string(), - SolanaNet::Devnet => "https://api.devnet.solana.com".to_string(), - } -} - -pub fn generate_key_pair_from_seed(seed: &str) -> Keypair { - let derivation_path = DerivationPath::from_str("m/44'/501'/0'").unwrap(); - let seed = bip39_seed_from_passphrase(seed).unwrap(); - - let ext = ExtendedSecretKey::from_seed(&seed.0) - .unwrap() - .derive(&derivation_path) - .unwrap(); - let pub_key = ext.public_key(); - let pair = ed25519_dalek::Keypair { - secret: ext.secret_key, - public: pub_key, - }; - - solana_sdk::signature::keypair_from_seed(pair.to_bytes().as_ref()).unwrap() -} - -pub fn generate_key_pair_from_iguana_seed(seed: String) -> Keypair { - let key_pair = key_pair_from_seed(seed.as_str()).unwrap(); - let secret_key = ed25519_dalek::SecretKey::from_bytes(key_pair.private().secret.as_slice()).unwrap(); - let public_key = ed25519_dalek::PublicKey::from(&secret_key); - let other_key_pair = ed25519_dalek::Keypair { - secret: secret_key, - public: public_key, - }; - solana_sdk::signature::keypair_from_seed(other_key_pair.to_bytes().as_ref()).unwrap() -} - -pub fn spl_coin_for_test( - solana_coin: SolanaCoin, - ticker: String, - decimals: u8, - token_contract_address: Pubkey, -) -> SplToken { - SplToken { - conf: Arc::new(SplTokenFields { - decimals, - ticker, - token_contract_address, - abortable_system: AbortableQueue::default(), - }), - platform_coin: solana_coin, - } -} - -pub fn solana_coin_for_test(seed: String, net_type: SolanaNet) -> (MmArc, SolanaCoin) { - let url = solana_net_to_url(net_type); - let client = RpcClient::new_with_commitment(url, CommitmentConfig { - commitment: CommitmentLevel::Finalized, - }); - let conf = json!({ - "coins":[ - {"coin":"SOL","name":"solana","protocol":{"type":"SOL"},"rpcport":80,"mm2":1} - ] - }); - let ctx = MmCtxBuilder::new().with_conf(conf).into_mm_arc(); - let (ticker, decimals) = ("SOL".to_string(), 8); - let key_pair = generate_key_pair_from_iguana_seed(seed); - let my_address = key_pair.pubkey().to_string(); - let spl_tokens_infos = Arc::new(Mutex::new(HashMap::new())); - let spawner = AbortableQueue::default(); - - let solana_coin = SolanaCoin(Arc::new(SolanaCoinImpl { - decimals, - my_address, - key_pair, - ticker, - client, - spl_tokens_infos, - abortable_system: spawner, - })); - (ctx, solana_coin) -} diff --git a/mm2src/coins/solana/solana_decode_tx_helpers.rs b/mm2src/coins/solana/solana_decode_tx_helpers.rs deleted file mode 100644 index bd22fc044e..0000000000 --- a/mm2src/coins/solana/solana_decode_tx_helpers.rs +++ /dev/null @@ -1,222 +0,0 @@ -extern crate serde_derive; - -use crate::{NumConversResult, SolanaCoin, SolanaFeeDetails, TransactionData, TransactionDetails, TransactionType}; -use mm2_number::BigDecimal; -use solana_sdk::native_token::lamports_to_sol; -use std::convert::TryFrom; - -#[derive(Debug, Serialize, Deserialize)] -pub struct SolanaConfirmedTransaction { - slot: u64, - transaction: Transaction, - meta: Meta, - #[serde(rename = "blockTime")] - block_time: u64, -} - -#[allow(dead_code)] -impl SolanaConfirmedTransaction { - pub fn extract_account_index(&self, address: String) -> usize { - // find the equivalent of index_of(needle) in rust, and return result later - let mut idx = 0_usize; - for account in self.transaction.message.account_keys.iter() { - if account.pubkey == address { - return idx; - } - idx += 1; - } - idx - } - - pub fn extract_solana_transactions(&self, solana_coin: &SolanaCoin) -> NumConversResult> { - let mut transactions = Vec::new(); - let account_idx = self.extract_account_index(solana_coin.my_address.clone()); - for instruction in self.transaction.message.instructions.iter() { - if instruction.is_solana_transfer() { - let lamports = instruction.parsed.info.lamports.unwrap_or_default(); - let amount = BigDecimal::try_from(lamports_to_sol(lamports))?; - let is_self_transfer = instruction.parsed.info.source == instruction.parsed.info.destination; - let am_i_sender = instruction.parsed.info.source == solana_coin.my_address; - let spent_by_me = if am_i_sender && !is_self_transfer { - amount.clone() - } else { - 0.into() - }; - let received_by_me = if is_self_transfer { amount.clone() } else { 0.into() }; - let my_balance_change = if am_i_sender { - BigDecimal::try_from(lamports_to_sol( - self.meta.pre_balances[account_idx] - self.meta.post_balances[account_idx], - ))? - } else { - BigDecimal::try_from(lamports_to_sol( - self.meta.post_balances[account_idx] - self.meta.pre_balances[account_idx], - ))? - }; - let fee = BigDecimal::try_from(lamports_to_sol(self.meta.fee))?; - let tx = TransactionDetails { - tx: TransactionData::new_signed(Default::default(), self.transaction.signatures[0].to_string()), - from: vec![instruction.parsed.info.source.clone()], - to: vec![instruction.parsed.info.destination.clone()], - total_amount: amount, - spent_by_me, - received_by_me, - my_balance_change, - block_height: self.slot, - timestamp: self.block_time, - fee_details: Some(SolanaFeeDetails { amount: fee }.into()), - coin: solana_coin.ticker.clone(), - internal_id: Default::default(), - kmd_rewards: None, - transaction_type: TransactionType::StandardTransfer, - memo: None, - }; - transactions.push(tx); - } - } - Ok(transactions) - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Meta { - err: Option, - status: Status, - fee: u64, - #[serde(rename = "preBalances")] - pre_balances: Vec, - #[serde(rename = "postBalances")] - post_balances: Vec, - #[serde(rename = "innerInstructions")] - inner_instructions: Vec>, - #[serde(rename = "logMessages")] - log_messages: Vec, - #[serde(rename = "preTokenBalances")] - pre_token_balances: Vec, - #[serde(rename = "postTokenBalances")] - post_token_balances: Vec, - rewards: Vec>, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct TokenBalance { - #[serde(rename = "accountIndex")] - account_index: u64, - mint: String, - #[serde(rename = "uiTokenAmount")] - ui_token_amount: TokenAmount, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct TokenAmount { - #[serde(rename = "uiAmount")] - ui_amount: f64, - decimals: u64, - amount: String, - #[serde(rename = "uiAmountString")] - ui_amount_string: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Status { - #[serde(rename = "Ok")] - ok: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Transaction { - signatures: Vec, - message: Message, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Message { - #[serde(rename = "accountKeys")] - account_keys: Vec, - #[serde(rename = "recentBlockhash")] - recent_blockhash: String, - instructions: Vec, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct AccountKey { - pubkey: String, - writable: bool, - signer: bool, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Instruction { - program: Program, - #[serde(rename = "programId")] - program_id: String, - parsed: Parsed, -} - -#[allow(dead_code)] -impl Instruction { - pub fn is_solana_transfer(&self) -> bool { - let is_system = match self.program { - Program::SplToken => return false, - Program::System => true, - }; - let is_transfer = match self.parsed.parsed_type { - Type::Transfer => true, - Type::TransferChecked => true, - Type::Unknown => false, - }; - is_system && is_transfer && self.parsed.info.lamports.is_some() - } - - // Will be used later - pub fn is_spl_transfer(&self) -> bool { - let is_spl_token = match self.program { - Program::SplToken => true, - Program::System => return false, - }; - let is_transfer = match self.parsed.parsed_type { - Type::Transfer => true, - Type::TransferChecked => true, - Type::Unknown => false, - }; - is_spl_token && is_transfer - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Parsed { - info: Info, - #[serde(rename = "type")] - parsed_type: Type, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Info { - destination: String, - lamports: Option, - source: String, - mint: Option, - #[serde(rename = "multisigAuthority")] - multisig_authority: Option, - signers: Option>, - #[serde(rename = "tokenAmount")] - token_amount: Option, - authority: Option, - amount: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum Type { - #[serde(rename = "transfer")] - Transfer, - #[serde(rename = "transferChecked")] - TransferChecked, - Unknown, -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum Program { - #[serde(rename = "spl-token")] - SplToken, - #[serde(rename = "system")] - System, -} diff --git a/mm2src/coins/solana/solana_tests.rs b/mm2src/coins/solana/solana_tests.rs deleted file mode 100644 index fb1a7b958c..0000000000 --- a/mm2src/coins/solana/solana_tests.rs +++ /dev/null @@ -1,337 +0,0 @@ -use super::*; -use crate::solana::solana_common_tests::{generate_key_pair_from_iguana_seed, generate_key_pair_from_seed, - solana_coin_for_test, SolanaNet}; -use crate::solana::solana_decode_tx_helpers::SolanaConfirmedTransaction; -use crate::MarketCoinOps; -use base58::ToBase58; -use common::{block_on, Future01CompatExt}; -use solana_client::rpc_request::TokenAccountsFilter; -use solana_sdk::signature::{Signature, Signer}; -use solana_transaction_status::UiTransactionEncoding; -use std::ops::Neg; -use std::str::FromStr; - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_keypair_from_secp() { - let solana_key_pair = generate_key_pair_from_iguana_seed("federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string()); - assert_eq!( - "FJktmyjV9aBHEShT4hfnLpr9ELywdwVtEL1w1rSWgbVf", - solana_key_pair.pubkey().to_string() - ); - - let other_solana_keypair = generate_key_pair_from_iguana_seed("bob passphrase".to_string()); - assert_eq!( - "B7KMMHyc3eYguUMneXRznY1NWh91HoVA2muVJetstYKE", - other_solana_keypair.pubkey().to_string() - ); -} - -// Research tests -// TODO remove `ignore` attribute once the test is stable. -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn solana_prerequisites() { - // same test as trustwallet - { - let fin = - generate_key_pair_from_seed("hood vacant left trim hard mushroom device flavor ask better arrest again"); - let public_address = fin.pubkey().to_string(); - let priv_key = &fin.secret().to_bytes()[..].to_base58(); - assert_eq!(public_address.len(), 44); - assert_eq!(public_address, "4rmosKwMH7zeaXGbej1PFybZBUyuUNQLf8RfyzCcYvkx"); - assert_eq!(priv_key, "CZtxt17aTfDrJrzwBWdVqcmFwVVptW8EX7RRnth9tT3M"); - let client = solana_client::rpc_client::RpcClient::new("https://api.testnet.solana.com/".to_string()); - let balance = client.get_balance(&fin.pubkey()).expect("Expect to retrieve balance"); - assert_eq!(balance, 0); - } - - { - let key_pair = generate_key_pair_from_iguana_seed("passphrase not really secure".to_string()); - let public_address = key_pair.pubkey().to_string(); - assert_eq!(public_address.len(), 44); - assert_eq!(public_address, "2jTgfhf98GosnKSCXjL5YSiEa3MLrmR42yy9kZZq1i2c"); - let client = solana_client::rpc_client::RpcClient::new("https://api.testnet.solana.com/".to_string()); - let balance = client - .get_balance(&key_pair.pubkey()) - .expect("Expect to retrieve balance"); - assert_eq!(lamports_to_sol(balance), BigDecimal::from(0)); - assert_eq!(balance, 0); - - // This will fetch all the balance from all tokens - let token_accounts = client - .get_token_accounts_by_owner(&key_pair.pubkey(), TokenAccountsFilter::ProgramId(spl_token::id())) - .expect(""); - log!("{:?}", token_accounts); - let actual_token_pubkey = solana_sdk::pubkey::Pubkey::from_str(token_accounts[0].pubkey.as_str()).unwrap(); - let amount = client.get_token_account_balance(&actual_token_pubkey).unwrap(); - assert_ne!(amount.ui_amount_string.as_str(), "0"); - } -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_coin_creation() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - assert_eq!( - sol_coin.my_address().unwrap(), - "FJktmyjV9aBHEShT4hfnLpr9ELywdwVtEL1w1rSWgbVf" - ); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_my_balance() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let res = block_on(sol_coin.my_balance().compat()).unwrap(); - assert_ne!(res.spendable, BigDecimal::from(0)); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_block_height() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let res = block_on(sol_coin.current_block().compat()).unwrap(); - log!("block is : {}", res); - assert!(res > 0); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_validate_address() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - - // invalid len - let res = sol_coin.validate_address("invalidaddressobviously"); - assert!(!res.is_valid); - let res = sol_coin.validate_address("GMtMFbuVgjDnzsBd3LLBfM4X8RyYcDGCM92tPq2PG6B2"); - assert!(res.is_valid); - - // Typo - let res = sol_coin.validate_address("Fr8fraJXAe1cFU81mF7NhHTrUzXjZAJkQE1gUQ11riH"); - assert!(!res.is_valid); - - // invalid len - let res = sol_coin.validate_address("r8fraJXAe1cFU81mF7NhHTrUzXjZAJkQE1gUQ11riHn"); - assert!(!res.is_valid); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_sign_message() { - let passphrase = "spice describe gravity federal blast come thank unfair canal monkey style afraid".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let signature = sol_coin.sign_message("test").unwrap(); - assert_eq!( - signature, - "4dzKwEteN8nch76zPMEjPX19RsaQwGTxsbtfg2bwGTkGenLfrdm31zvn9GH5rvaJBwivp6ESXx1KYR672ngs3UfF" - ); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_verify_message() { - let passphrase = "spice describe gravity federal blast come thank unfair canal monkey style afraid".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let is_valid = sol_coin - .verify_message( - "4dzKwEteN8nch76zPMEjPX19RsaQwGTxsbtfg2bwGTkGenLfrdm31zvn9GH5rvaJBwivp6ESXx1KYR672ngs3UfF", - "test", - "8UF6jSVE1jW8mSiGqt8Hft1rLwPjdKLaTfhkNozFwoAG", - ) - .unwrap(); - assert!(is_valid); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_transaction_simulations() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Devnet); - let request_amount = BigDecimal::try_from(0.0001).unwrap(); - let valid_tx_details = block_on( - sol_coin - .withdraw(WithdrawRequest { - coin: "SOL".to_string(), - from: None, - to: sol_coin.my_address.clone(), - amount: request_amount.clone(), - max: false, - fee: None, - memo: None, - ibc_source_channel: None, - }) - .compat(), - ) - .unwrap(); - let (_, fees) = block_on(sol_coin.estimate_withdraw_fees()).unwrap(); - let sol_required = lamports_to_sol(fees); - let expected_spent_by_me = &request_amount + &sol_required; - assert_eq!(valid_tx_details.spent_by_me, expected_spent_by_me); - assert_eq!(valid_tx_details.received_by_me, request_amount); - assert_eq!(valid_tx_details.total_amount, expected_spent_by_me); - assert_eq!(valid_tx_details.my_balance_change, sol_required.neg()); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_transaction_zero_balance() { - let passphrase = "fake passphrase".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Devnet); - let invalid_tx_details = block_on( - sol_coin - .withdraw(WithdrawRequest { - coin: "SOL".to_string(), - from: None, - to: sol_coin.my_address.clone(), - amount: BigDecimal::from_str("0.000001").unwrap(), - max: false, - fee: None, - memo: None, - ibc_source_channel: None, - }) - .compat(), - ); - let error = invalid_tx_details.unwrap_err(); - let (_, fees) = block_on(sol_coin.estimate_withdraw_fees()).unwrap(); - let sol_required = lamports_to_sol(fees); - match error.into_inner() { - WithdrawError::NotSufficientBalance { required, .. } => { - assert_eq!(required, sol_required); - }, - e => panic!("Unexpected err {:?}", e), - }; -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_transaction_simulations_not_enough_for_fees() { - let passphrase = "non existent passphrase".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Devnet); - let invalid_tx_details = block_on( - sol_coin - .withdraw(WithdrawRequest { - coin: "SOL".to_string(), - from: None, - to: sol_coin.my_address.clone(), - amount: BigDecimal::from(1), - max: false, - fee: None, - memo: None, - ibc_source_channel: None, - }) - .compat(), - ); - let error = invalid_tx_details.unwrap_err(); - let (_, fees) = block_on(sol_coin.estimate_withdraw_fees()).unwrap(); - let sol_required = lamports_to_sol(fees); - match error.into_inner() { - WithdrawError::NotSufficientBalance { - coin: _, - available, - required, - } => { - assert_eq!(available, 0.into()); - assert_eq!(required, sol_required); - }, - e => panic!("Unexpected err {:?}", e), - }; -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_transaction_simulations_max() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Devnet); - let valid_tx_details = block_on( - sol_coin - .withdraw(WithdrawRequest { - coin: "SOL".to_string(), - from: None, - to: sol_coin.my_address.clone(), - amount: BigDecimal::from(0), - max: true, - fee: None, - memo: None, - ibc_source_channel: None, - }) - .compat(), - ) - .unwrap(); - let balance = block_on(sol_coin.my_balance().compat()).unwrap().spendable; - let (_, fees) = block_on(sol_coin.estimate_withdraw_fees()).unwrap(); - let sol_required = lamports_to_sol(fees); - assert_eq!(valid_tx_details.my_balance_change, sol_required.clone().neg()); - assert_eq!(valid_tx_details.total_amount, balance); - assert_eq!(valid_tx_details.spent_by_me, balance); - assert_eq!(valid_tx_details.received_by_me, &balance - &sol_required); - log!("{:?}", valid_tx_details); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_test_transactions() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Devnet); - let valid_tx_details = block_on( - sol_coin - .withdraw(WithdrawRequest { - coin: "SOL".to_string(), - from: None, - to: sol_coin.my_address.clone(), - amount: BigDecimal::try_from(0.0001).unwrap(), - max: false, - fee: None, - memo: None, - ibc_source_channel: None, - }) - .compat(), - ) - .unwrap(); - log!("{:?}", valid_tx_details); - - let tx_str = hex::encode(&*valid_tx_details.tx.tx_hex().unwrap().0); - let res = block_on(sol_coin.send_raw_tx(&tx_str).compat()).unwrap(); - - let res2 = block_on( - sol_coin - .send_raw_tx_bytes(&valid_tx_details.tx.tx_hex().unwrap().0) - .compat(), - ) - .unwrap(); - assert_eq!(res, res2); - - //log!("{:?}", res); -} - -// This test is just a unit test for brainstorming around tx_history for base_coin. -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn solana_test_tx_history() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let res = sol_coin - .client - .get_signatures_for_address(&sol_coin.key_pair.pubkey()) - .unwrap(); - let mut history = Vec::new(); - for cur in res.iter() { - let signature = Signature::from_str(cur.signature.clone().as_str()).unwrap(); - let res = sol_coin - .client - .get_transaction(&signature, UiTransactionEncoding::JsonParsed) - .unwrap(); - log!("{}", serde_json::to_string(&res).unwrap()); - let parsed = serde_json::to_value(&res).unwrap(); - let tx_infos: SolanaConfirmedTransaction = serde_json::from_value(parsed).unwrap(); - let mut txs = tx_infos.extract_solana_transactions(&sol_coin).unwrap(); - history.append(&mut txs); - } - log!("{}", serde_json::to_string(&history).unwrap()); -} diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs deleted file mode 100644 index 7bc720e5e7..0000000000 --- a/mm2src/coins/solana/spl.rs +++ /dev/null @@ -1,600 +0,0 @@ -use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, SwapOps, TradeFee, TransactionEnum, WatcherOps}; -use crate::coin_errors::{MyAddressError, ValidatePaymentResult}; -use crate::solana::solana_common::{ui_amount_to_amount, PrepareTransferData, SufficientBalanceError}; -use crate::solana::{solana_common, AccountError, SolanaCommonOps, SolanaFeeDetails}; -use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, DexFee, FeeApproxStage, - FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, - PaymentInstructions, PaymentInstructionsErr, RawTransactionError, RawTransactionFut, - RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, - SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, - SignatureResult, SolanaCoin, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TransactionData, TransactionDetails, TransactionFut, TransactionResult, - TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, - WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; -use async_trait::async_trait; -use bincode::serialize; -use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError}; -use common::{async_blocking, now_sec}; -use futures::{FutureExt, TryFutureExt}; -use futures01::Future; -use keys::KeyPair; -use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::prelude::*; -use mm2_number::{BigDecimal, MmNumber}; -use rpc::v1::types::Bytes as BytesJson; -use serde_json::Value as Json; -use solana_client::{rpc_client::RpcClient, rpc_request::TokenAccountsFilter}; -use solana_sdk::message::Message; -use solana_sdk::transaction::Transaction; -use solana_sdk::{pubkey::Pubkey, signature::Signer}; -use spl_associated_token_account::{create_associated_token_account, get_associated_token_address}; -use std::{convert::TryFrom, - fmt::{Debug, Formatter, Result as FmtResult}, - str::FromStr, - sync::Arc}; - -#[derive(Debug)] -pub enum SplTokenCreationError { - InvalidPubkey(String), - Internal(String), -} - -impl From for SplTokenCreationError { - fn from(e: AbortedError) -> Self { SplTokenCreationError::Internal(e.to_string()) } -} - -pub struct SplTokenFields { - pub decimals: u8, - pub ticker: String, - pub token_contract_address: Pubkey, - pub abortable_system: AbortableQueue, -} - -#[derive(Clone, Debug)] -pub struct SplTokenInfo { - pub token_contract_address: Pubkey, - pub decimals: u8, -} - -#[derive(Debug)] -pub struct SplProtocolConf { - pub platform_coin_ticker: String, - pub decimals: u8, - pub token_contract_address: String, -} - -#[derive(Clone)] -pub struct SplToken { - pub conf: Arc, - pub platform_coin: SolanaCoin, -} - -impl Debug for SplToken { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { f.write_str(&self.conf.ticker) } -} - -impl SplToken { - pub fn new( - decimals: u8, - ticker: String, - token_address: String, - platform_coin: SolanaCoin, - ) -> MmResult { - let token_contract_address = solana_sdk::pubkey::Pubkey::from_str(&token_address) - .map_err(|e| MmError::new(SplTokenCreationError::InvalidPubkey(format!("{:?}", e))))?; - - // Create an abortable system linked to the `platform_coin` so if the platform coin is disabled, - // all spawned futures related to `SplToken` will be aborted as well. - let abortable_system = platform_coin.abortable_system.create_subsystem()?; - - let conf = Arc::new(SplTokenFields { - decimals, - ticker, - token_contract_address, - abortable_system, - }); - Ok(SplToken { conf, platform_coin }) - } - - pub fn get_info(&self) -> SplTokenInfo { - SplTokenInfo { - token_contract_address: self.conf.token_contract_address, - decimals: self.decimals(), - } - } -} - -async fn withdraw_spl_token_impl(coin: SplToken, req: WithdrawRequest) -> WithdrawResult { - let (hash, fees) = coin.platform_coin.estimate_withdraw_fees().await?; - let res = coin - .check_balance_and_prepare_transfer(req.max, req.amount.clone(), fees) - .await?; - let system_destination_pubkey = solana_sdk::pubkey::Pubkey::try_from(&*req.to)?; - let contract_key = coin.get_underlying_contract_pubkey(); - let auth_key = coin.platform_coin.key_pair.pubkey(); - let funding_address = coin.get_pubkey().await?; - let dest_token_address = get_associated_token_address(&system_destination_pubkey, &contract_key); - let mut instructions = Vec::with_capacity(1); - let account_info = async_blocking({ - let coin = coin.clone(); - move || coin.rpc().get_account(&dest_token_address) - }) - .await; - if account_info.is_err() { - let instruction_creation = create_associated_token_account(&auth_key, &dest_token_address, &contract_key); - instructions.push(instruction_creation); - } - let amount = ui_amount_to_amount(req.amount, coin.conf.decimals)?; - let instruction_transfer_checked = spl_token::instruction::transfer_checked( - &spl_token::id(), - &funding_address, - &contract_key, - &dest_token_address, - &auth_key, - &[&auth_key], - amount, - coin.conf.decimals, - )?; - instructions.push(instruction_transfer_checked); - let msg = Message::new(&instructions, Some(&auth_key)); - let signers = vec![&coin.platform_coin.key_pair]; - let tx = Transaction::new(&signers, msg, hash); - let serialized_tx = serialize(&tx).map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; - let received_by_me = if req.to == coin.platform_coin.my_address { - res.to_send.clone() - } else { - 0.into() - }; - Ok(TransactionDetails { - tx: TransactionData::new_signed(serialized_tx.into(), tx.signatures[0].to_string()), - from: vec![coin.platform_coin.my_address.clone()], - to: vec![req.to], - total_amount: res.to_send.clone(), - spent_by_me: res.to_send.clone(), - my_balance_change: &received_by_me - &res.to_send, - received_by_me, - block_height: 0, - timestamp: now_sec(), - fee_details: Some( - SolanaFeeDetails { - amount: res.sol_required, - } - .into(), - ), - coin: coin.conf.ticker.clone(), - internal_id: vec![].into(), - kmd_rewards: None, - transaction_type: TransactionType::StandardTransfer, - memo: None, - }) -} - -async fn withdraw_impl(coin: SplToken, req: WithdrawRequest) -> WithdrawResult { - let validate_address_result = coin.validate_address(&req.to); - if !validate_address_result.is_valid { - return MmError::err(WithdrawError::InvalidAddress( - validate_address_result.reason.unwrap_or_else(|| "Unknown".to_string()), - )); - } - withdraw_spl_token_impl(coin, req).await -} - -#[async_trait] -impl SolanaCommonOps for SplToken { - fn rpc(&self) -> &RpcClient { &self.platform_coin.client } - - fn is_token(&self) -> bool { true } - - async fn check_balance_and_prepare_transfer( - &self, - max: bool, - amount: BigDecimal, - fees: u64, - ) -> Result> { - solana_common::check_balance_and_prepare_transfer(self, max, amount, fees).await - } -} - -impl SplToken { - fn get_underlying_contract_pubkey(&self) -> Pubkey { self.conf.token_contract_address } - - async fn get_pubkey(&self) -> Result> { - let coin = self.clone(); - let token_accounts = async_blocking(move || { - coin.rpc().get_token_accounts_by_owner( - &coin.platform_coin.key_pair.pubkey(), - TokenAccountsFilter::Mint(coin.get_underlying_contract_pubkey()), - ) - }) - .await?; - if token_accounts.is_empty() { - return MmError::err(AccountError::NotFundedError("account_not_funded".to_string())); - } - Ok(Pubkey::from_str(&token_accounts[0].pubkey)?) - } - - fn my_balance_impl(&self) -> BalanceFut { - let coin = self.clone(); - let fut = async move { - coin.platform_coin - .my_balance_spl(SplTokenInfo { - token_contract_address: coin.conf.token_contract_address, - decimals: coin.conf.decimals, - }) - .await - }; - Box::new(fut.boxed().compat()) - } -} - -#[async_trait] -impl MarketCoinOps for SplToken { - fn ticker(&self) -> &str { &self.conf.ticker } - - fn my_address(&self) -> MmResult { Ok(self.platform_coin.my_address.clone()) } - - async fn get_public_key(&self) -> Result> { unimplemented!() } - - fn sign_message_hash(&self, _message: &str) -> Option<[u8; 32]> { unimplemented!() } - - fn sign_message(&self, message: &str) -> SignatureResult { - solana_common::sign_message(&self.platform_coin, message) - } - - fn verify_message(&self, signature: &str, message: &str, pubkey_bs58: &str) -> VerificationResult { - solana_common::verify_message(&self.platform_coin, signature, message, pubkey_bs58) - } - - fn my_balance(&self) -> BalanceFut { - let fut = self.my_balance_impl().and_then(Ok); - Box::new(fut) - } - - fn base_coin_balance(&self) -> BalanceFut { self.platform_coin.base_coin_balance() } - - fn platform_ticker(&self) -> &str { self.platform_coin.ticker() } - - #[inline(always)] - fn send_raw_tx(&self, tx: &str) -> Box + Send> { - self.platform_coin.send_raw_tx(tx) - } - - #[inline(always)] - fn send_raw_tx_bytes(&self, tx: &[u8]) -> Box + Send> { - self.platform_coin.send_raw_tx_bytes(tx) - } - - #[inline(always)] - async fn sign_raw_tx(&self, _args: &SignRawTransactionRequest) -> RawTransactionResult { - MmError::err(RawTransactionError::NotImplemented { - coin: self.ticker().to_string(), - }) - } - - fn wait_for_confirmations(&self, _input: ConfirmPaymentInput) -> Box + Send> { - unimplemented!() - } - - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { unimplemented!() } - - fn tx_enum_from_bytes(&self, _bytes: &[u8]) -> Result> { - MmError::err(TxMarshalingErr::NotSupported( - "tx_enum_from_bytes is not supported for Spl yet.".to_string(), - )) - } - - fn current_block(&self) -> Box + Send> { self.platform_coin.current_block() } - - fn display_priv_key(&self) -> Result { self.platform_coin.display_priv_key() } - - fn min_tx_amount(&self) -> BigDecimal { BigDecimal::from(0) } - - fn min_trading_vol(&self) -> MmNumber { MmNumber::from("0.00777") } - - fn is_trezor(&self) -> bool { self.platform_coin.is_trezor() } -} - -#[async_trait] -impl SwapOps for SplToken { - fn send_taker_fee(&self, _fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { - unimplemented!() - } - - fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - - fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - - async fn send_maker_spends_taker_payment( - &self, - _maker_spends_payment_args: SpendPaymentArgs<'_>, - ) -> TransactionResult { - unimplemented!() - } - - async fn send_taker_spends_maker_payment( - &self, - _taker_spends_payment_args: SpendPaymentArgs<'_>, - ) -> TransactionResult { - unimplemented!() - } - - async fn send_taker_refunds_payment( - &self, - _taker_refunds_payment_args: RefundPaymentArgs<'_>, - ) -> TransactionResult { - unimplemented!() - } - - async fn send_maker_refunds_payment( - &self, - _maker_refunds_payment_args: RefundPaymentArgs<'_>, - ) -> TransactionResult { - todo!() - } - - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } - - async fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { - unimplemented!() - } - - async fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { - unimplemented!() - } - - fn check_if_my_payment_sent( - &self, - _if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { - unimplemented!() - } - - async fn search_for_swap_tx_spend_my( - &self, - _: SearchForSwapTxSpendInput<'_>, - ) -> Result, String> { - unimplemented!() - } - - async fn search_for_swap_tx_spend_other( - &self, - _: SearchForSwapTxSpendInput<'_>, - ) -> Result, String> { - unimplemented!() - } - - fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result> { - unimplemented!(); - } - - async fn extract_secret( - &self, - secret_hash: &[u8], - spend_tx: &[u8], - watcher_reward: bool, - ) -> Result, String> { - unimplemented!() - } - - fn is_auto_refundable(&self) -> bool { false } - - async fn wait_for_htlc_refund(&self, _tx: &[u8], _locktime: u64) -> RefundResult<()> { - MmError::err(RefundError::Internal( - "wait_for_htlc_refund is not supported for this coin!".into(), - )) - } - - fn negotiate_swap_contract_addr( - &self, - _other_side_address: Option<&[u8]>, - ) -> Result, MmError> { - unimplemented!() - } - - #[inline] - fn derive_htlc_key_pair(&self, _swap_unique_data: &[u8]) -> KeyPair { todo!() } - - #[inline] - fn derive_htlc_pubkey(&self, swap_unique_data: &[u8]) -> Vec { - self.derive_htlc_key_pair(swap_unique_data).public_slice().to_vec() - } - - fn validate_other_pubkey(&self, _raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { unimplemented!() } - - async fn maker_payment_instructions( - &self, - _args: PaymentInstructionArgs<'_>, - ) -> Result>, MmError> { - unimplemented!() - } - - async fn taker_payment_instructions( - &self, - _args: PaymentInstructionArgs<'_>, - ) -> Result>, MmError> { - unimplemented!() - } - - fn validate_maker_payment_instructions( - &self, - _instructions: &[u8], - _args: PaymentInstructionArgs<'_>, - ) -> Result> { - unimplemented!() - } - - fn validate_taker_payment_instructions( - &self, - _instructions: &[u8], - _args: PaymentInstructionArgs<'_>, - ) -> Result> { - unimplemented!() - } -} - -#[async_trait] -impl TakerSwapMakerCoin for SplToken { - async fn on_taker_payment_refund_start(&self, _maker_payment: &[u8]) -> RefundResult<()> { Ok(()) } - - async fn on_taker_payment_refund_success(&self, _maker_payment: &[u8]) -> RefundResult<()> { Ok(()) } -} - -#[async_trait] -impl MakerSwapTakerCoin for SplToken { - async fn on_maker_payment_refund_start(&self, _taker_payment: &[u8]) -> RefundResult<()> { Ok(()) } - - async fn on_maker_payment_refund_success(&self, _taker_payment: &[u8]) -> RefundResult<()> { Ok(()) } -} - -#[async_trait] -impl WatcherOps for SplToken { - fn send_maker_payment_spend_preimage(&self, _input: SendMakerPaymentSpendPreimageInput) -> TransactionFut { - unimplemented!(); - } - - fn create_taker_payment_refund_preimage( - &self, - _taker_payment_tx: &[u8], - _time_lock: u64, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_contract_address: &Option, - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - - fn create_maker_payment_spend_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u64, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - - fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - unimplemented!(); - } - - fn watcher_validate_taker_fee(&self, _input: WatcherValidateTakerFeeInput) -> ValidatePaymentFut<()> { - unimplemented!(); - } - - fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { - unimplemented!(); - } - - fn taker_validates_payment_spend_or_refund(&self, _input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { - unimplemented!() - } - - async fn watcher_search_for_swap_tx_spend( - &self, - input: WatcherSearchForSwapTxSpendInput<'_>, - ) -> Result, String> { - unimplemented!(); - } - - async fn get_taker_watcher_reward( - &self, - other_coin: &MmCoinEnum, - coin_amount: Option, - other_coin_amount: Option, - reward_amount: Option, - wait_until: u64, - ) -> Result> { - unimplemented!(); - } - - async fn get_maker_watcher_reward( - &self, - other_coin: &MmCoinEnum, - reward_amount: Option, - wait_until: u64, - ) -> Result, MmError> { - unimplemented!(); - } -} - -#[async_trait] -impl MmCoin for SplToken { - fn is_asset_chain(&self) -> bool { false } - - fn spawner(&self) -> CoinFutSpawner { CoinFutSpawner::new(&self.conf.abortable_system) } - - fn withdraw(&self, req: WithdrawRequest) -> WithdrawFut { - Box::new(Box::pin(withdraw_impl(self.clone(), req)).compat()) - } - - fn get_raw_transaction(&self, _req: RawTransactionRequest) -> RawTransactionFut { unimplemented!() } - - fn get_tx_hex_by_hash(&self, tx_hash: Vec) -> RawTransactionFut { unimplemented!() } - - fn decimals(&self) -> u8 { self.conf.decimals } - - fn convert_to_address(&self, _from: &str, _to_address_format: Json) -> Result { unimplemented!() } - - fn validate_address(&self, address: &str) -> ValidateAddressResult { self.platform_coin.validate_address(address) } - - fn process_history_loop(&self, _ctx: MmArc) -> Box + Send> { unimplemented!() } - - fn history_sync_status(&self) -> HistorySyncState { unimplemented!() } - - /// Get fee to be paid per 1 swap transaction - fn get_trade_fee(&self) -> Box + Send> { unimplemented!() } - - async fn get_sender_trade_fee( - &self, - _value: TradePreimageValue, - _stage: FeeApproxStage, - _include_refund_fee: bool, - ) -> TradePreimageResult { - unimplemented!() - } - - fn get_receiver_trade_fee(&self, _stage: FeeApproxStage) -> TradePreimageFut { unimplemented!() } - - async fn get_fee_to_send_taker_fee( - &self, - _dex_fee_amount: DexFee, - _stage: FeeApproxStage, - ) -> TradePreimageResult { - unimplemented!() - } - - fn required_confirmations(&self) -> u64 { 1 } - - fn requires_notarization(&self) -> bool { false } - - fn set_required_confirmations(&self, _confirmations: u64) { unimplemented!() } - - fn set_requires_notarization(&self, _requires_nota: bool) { unimplemented!() } - - fn swap_contract_address(&self) -> Option { unimplemented!() } - - fn fallback_swap_contract(&self) -> Option { unimplemented!() } - - fn mature_confirmations(&self) -> Option { Some(1) } - - fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { Vec::new() } - - fn is_coin_protocol_supported( - &self, - _info: &Option>, - _amount_to_send: Option, - _locktime: u64, - _is_maker: bool, - ) -> bool { - true - } - - fn on_disabled(&self) -> Result<(), AbortedError> { self.conf.abortable_system.abort_all() } - - fn on_token_deactivated(&self, _ticker: &str) {} -} diff --git a/mm2src/coins/solana/spl_tests.rs b/mm2src/coins/solana/spl_tests.rs deleted file mode 100644 index 10943e6e33..0000000000 --- a/mm2src/coins/solana/spl_tests.rs +++ /dev/null @@ -1,138 +0,0 @@ -use super::*; -use crate::{solana::solana_common_tests::solana_coin_for_test, - solana::solana_common_tests::{spl_coin_for_test, SolanaNet}}; -use common::{block_on, Future01CompatExt}; -use std::ops::Neg; -use std::str::FromStr; - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn spl_coin_creation() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let sol_spl_usdc_coin = spl_coin_for_test( - sol_coin, - "USDC".to_string(), - 6, - solana_sdk::pubkey::Pubkey::from_str("CpMah17kQEL2wqyMKt3mZBdTnZbkbfx4nqmQMFDP5vwp").unwrap(), - ); - - log!("address: {}", sol_spl_usdc_coin.my_address().unwrap()); - assert_eq!( - sol_spl_usdc_coin.my_address().unwrap(), - "FJktmyjV9aBHEShT4hfnLpr9ELywdwVtEL1w1rSWgbVf" - ); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_sign_message() { - let passphrase = "spice describe gravity federal blast come thank unfair canal monkey style afraid".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let sol_spl_usdc_coin = spl_coin_for_test( - sol_coin, - "USDC".to_string(), - 6, - solana_sdk::pubkey::Pubkey::from_str("CpMah17kQEL2wqyMKt3mZBdTnZbkbfx4nqmQMFDP5vwp").unwrap(), - ); - let signature = sol_spl_usdc_coin.sign_message("test").unwrap(); - assert_eq!( - signature, - "4dzKwEteN8nch76zPMEjPX19RsaQwGTxsbtfg2bwGTkGenLfrdm31zvn9GH5rvaJBwivp6ESXx1KYR672ngs3UfF" - ); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_verify_message() { - let passphrase = "spice describe gravity federal blast come thank unfair canal monkey style afraid".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let sol_spl_usdc_coin = spl_coin_for_test( - sol_coin, - "USDC".to_string(), - 6, - solana_sdk::pubkey::Pubkey::from_str("CpMah17kQEL2wqyMKt3mZBdTnZbkbfx4nqmQMFDP5vwp").unwrap(), - ); - let is_valid = sol_spl_usdc_coin - .verify_message( - "4dzKwEteN8nch76zPMEjPX19RsaQwGTxsbtfg2bwGTkGenLfrdm31zvn9GH5rvaJBwivp6ESXx1KYR672ngs3UfF", - "test", - "8UF6jSVE1jW8mSiGqt8Hft1rLwPjdKLaTfhkNozFwoAG", - ) - .unwrap(); - assert!(is_valid); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn spl_my_balance() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let sol_spl_usdc_coin = spl_coin_for_test( - sol_coin.clone(), - "USDC".to_string(), - 6, - solana_sdk::pubkey::Pubkey::from_str("CpMah17kQEL2wqyMKt3mZBdTnZbkbfx4nqmQMFDP5vwp").unwrap(), - ); - - let res = block_on(sol_spl_usdc_coin.my_balance().compat()).unwrap(); - assert_ne!(res.spendable, BigDecimal::from(0)); - assert!(res.spendable < BigDecimal::from(10)); - - let sol_spl_wsol_coin = spl_coin_for_test( - sol_coin, - "WSOL".to_string(), - 8, - solana_sdk::pubkey::Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap(), - ); - let res = block_on(sol_spl_wsol_coin.my_balance().compat()).unwrap(); - assert_eq!(res.spendable, BigDecimal::from(0)); -} - -// Stop ignoring when Solana is released -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn test_spl_transactions() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let usdc_sol_coin = spl_coin_for_test( - sol_coin, - "USDC".to_string(), - 6, - solana_sdk::pubkey::Pubkey::from_str("CpMah17kQEL2wqyMKt3mZBdTnZbkbfx4nqmQMFDP5vwp").unwrap(), - ); - let withdraw_amount = BigDecimal::from_str("0.0001").unwrap(); - let valid_tx_details = block_on( - usdc_sol_coin - .withdraw(WithdrawRequest { - coin: "USDC".to_string(), - from: None, - to: "AYJmtzc9D4KU6xsDzhKShFyYKUNXY622j9QoQEo4LfpX".to_string(), - amount: withdraw_amount.clone(), - max: false, - fee: None, - memo: None, - ibc_source_channel: None, - }) - .compat(), - ) - .unwrap(); - log!("{:?}", valid_tx_details); - assert_eq!(valid_tx_details.total_amount, withdraw_amount); - assert_eq!(valid_tx_details.my_balance_change, withdraw_amount.neg()); - assert_eq!(valid_tx_details.coin, "USDC".to_string()); - assert_ne!(valid_tx_details.timestamp, 0); - - let tx_str = hex::encode(&*valid_tx_details.tx.tx_hex().unwrap().0); - let res = block_on(usdc_sol_coin.send_raw_tx(&tx_str).compat()).unwrap(); - log!("{:?}", res); - - let res2 = block_on( - usdc_sol_coin - .send_raw_tx_bytes(&valid_tx_details.tx.tx_hex().unwrap().0) - .compat(), - ) - .unwrap(); - assert_eq!(res, res2); -} diff --git a/mm2src/coins/tendermint/htlc/iris/htlc.rs b/mm2src/coins/tendermint/htlc/iris/htlc.rs index 09561a4048..59cfb5cd73 100644 --- a/mm2src/coins/tendermint/htlc/iris/htlc.rs +++ b/mm2src/coins/tendermint/htlc/iris/htlc.rs @@ -1,12 +1,9 @@ use super::htlc_proto::{IrisClaimHtlcProto, IrisCreateHtlcProto}; -use cosmrs::proto::traits::TypeUrl; +use cosmrs::proto::traits::Name; use cosmrs::{tx::Msg, AccountId, Coin, ErrorReport}; use std::convert::TryFrom; -pub(crate) const IRIS_CREATE_HTLC_TYPE_URL: &str = "/irismod.htlc.MsgCreateHTLC"; -pub(crate) const IRIS_CLAIM_HTLC_TYPE_URL: &str = "/irismod.htlc.MsgClaimHTLC"; - #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct IrisCreateHtlcMsg { /// Sender's address. @@ -87,8 +84,9 @@ impl From<&IrisCreateHtlcMsg> for IrisCreateHtlcProto { } } -impl TypeUrl for IrisCreateHtlcProto { - const TYPE_URL: &'static str = IRIS_CREATE_HTLC_TYPE_URL; +impl Name for IrisCreateHtlcProto { + const NAME: &'static str = "MsgCreateHTLC"; + const PACKAGE: &'static str = "irismod.htlc"; } #[derive(Clone)] @@ -141,6 +139,7 @@ impl From<&IrisClaimHtlcMsg> for IrisClaimHtlcProto { } } -impl TypeUrl for IrisClaimHtlcProto { - const TYPE_URL: &'static str = IRIS_CLAIM_HTLC_TYPE_URL; +impl Name for IrisClaimHtlcProto { + const NAME: &'static str = "MsgClaimHTLC"; + const PACKAGE: &'static str = "irismod.htlc"; } diff --git a/mm2src/coins/tendermint/htlc/mod.rs b/mm2src/coins/tendermint/htlc/mod.rs index 35a093b1d3..5675c915f2 100644 --- a/mm2src/coins/tendermint/htlc/mod.rs +++ b/mm2src/coins/tendermint/htlc/mod.rs @@ -85,7 +85,7 @@ pub(crate) struct TendermintHtlc { pub(crate) id: String, /// Message payload to be sent. - pub(crate) msg_payload: cosmrs::Any, + pub(crate) msg_payload: Any, } #[derive(prost::Message)] diff --git a/mm2src/coins/tendermint/htlc/nucleus/htlc.rs b/mm2src/coins/tendermint/htlc/nucleus/htlc.rs index a768f7fd4a..2c423285bf 100644 --- a/mm2src/coins/tendermint/htlc/nucleus/htlc.rs +++ b/mm2src/coins/tendermint/htlc/nucleus/htlc.rs @@ -1,12 +1,9 @@ use super::htlc_proto::{NucleusClaimHtlcProto, NucleusCreateHtlcProto}; -use cosmrs::proto::traits::TypeUrl; +use cosmrs::proto::traits::Name; use cosmrs::{tx::Msg, AccountId, Coin, ErrorReport}; use std::convert::TryFrom; -pub(crate) const NUCLEUS_CREATE_HTLC_TYPE_URL: &str = "/nucleus.htlc.MsgCreateHTLC"; -pub(crate) const NUCLEUS_CLAIM_HTLC_TYPE_URL: &str = "/nucleus.htlc.MsgClaimHTLC"; - #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct NucleusCreateHtlcMsg { /// Sender's address. @@ -72,8 +69,9 @@ impl From<&NucleusCreateHtlcMsg> for NucleusCreateHtlcProto { } } -impl TypeUrl for NucleusCreateHtlcProto { - const TYPE_URL: &'static str = NUCLEUS_CREATE_HTLC_TYPE_URL; +impl Name for NucleusCreateHtlcProto { + const NAME: &'static str = "MsgCreateHTLC"; + const PACKAGE: &'static str = "nucleus.htlc"; } #[derive(Clone)] @@ -126,6 +124,7 @@ impl From<&NucleusClaimHtlcMsg> for NucleusClaimHtlcProto { } } -impl TypeUrl for NucleusClaimHtlcProto { - const TYPE_URL: &'static str = NUCLEUS_CLAIM_HTLC_TYPE_URL; +impl Name for NucleusClaimHtlcProto { + const NAME: &'static str = "MsgClaimHTLC"; + const PACKAGE: &'static str = "nucleus.htlc"; } diff --git a/mm2src/coins/tendermint/ibc/mod.rs b/mm2src/coins/tendermint/ibc/mod.rs index 51df375ab1..9e1c905398 100644 --- a/mm2src/coins/tendermint/ibc/mod.rs +++ b/mm2src/coins/tendermint/ibc/mod.rs @@ -1,7 +1,6 @@ mod ibc_proto; pub(crate) mod transfer_v1; -pub(crate) const IBC_TRANSFER_TYPE_URL: &str = "/ibc.applications.transfer.v1.MsgTransfer"; pub(crate) const IBC_OUT_SOURCE_PORT: &str = "transfer"; pub(crate) const IBC_OUT_TIMEOUT_IN_NANOS: u64 = 60000000000 * 15; // 15 minutes pub(crate) const IBC_GAS_LIMIT_DEFAULT: u64 = 150_000; diff --git a/mm2src/coins/tendermint/ibc/transfer_v1.rs b/mm2src/coins/tendermint/ibc/transfer_v1.rs index c5780e32b7..22bad1fefe 100644 --- a/mm2src/coins/tendermint/ibc/transfer_v1.rs +++ b/mm2src/coins/tendermint/ibc/transfer_v1.rs @@ -1,6 +1,5 @@ use super::{ibc_proto::IBCTransferV1Proto, IBC_OUT_SOURCE_PORT, IBC_OUT_TIMEOUT_IN_NANOS}; -use crate::tendermint::ibc::IBC_TRANSFER_TYPE_URL; -use cosmrs::proto::traits::TypeUrl; +use cosmrs::proto::traits::Name; use cosmrs::{tx::Msg, AccountId, Coin, ErrorReport}; use std::convert::TryFrom; @@ -99,6 +98,7 @@ impl From<&MsgTransfer> for IBCTransferV1Proto { } } -impl TypeUrl for IBCTransferV1Proto { - const TYPE_URL: &'static str = IBC_TRANSFER_TYPE_URL; +impl Name for IBCTransferV1Proto { + const NAME: &'static str = "MsgTransfer"; + const PACKAGE: &'static str = "ibc.applications.transfer.v1"; } diff --git a/mm2src/coins/tendermint/rpc/tendermint_native_rpc.rs b/mm2src/coins/tendermint/rpc/tendermint_native_rpc.rs index 27ae5e7a0e..5da40d622d 100644 --- a/mm2src/coins/tendermint/rpc/tendermint_native_rpc.rs +++ b/mm2src/coins/tendermint/rpc/tendermint_native_rpc.rs @@ -5,6 +5,8 @@ use cosmrs::tendermint::block::Height; use cosmrs::tendermint::evidence::Evidence; use cosmrs::tendermint::Genesis; use cosmrs::tendermint::Hash; +use http::Uri; +use mm2_p2p::Keypair; use serde::{de::DeserializeOwned, Serialize}; use std::fmt; use std::time::Duration; @@ -292,22 +294,25 @@ pub struct HttpClient { impl HttpClient { /// Construct a new Tendermint RPC HTTP/S client connecting to the given /// URL. - pub fn new(url: U) -> Result + pub fn new(url: U, proxy_sign_keypair: Option) -> Result where U: TryInto, { let url = url.try_into()?; Ok(Self { inner: if url.0.is_secure() { - sealed::HttpClient::new_https(url.try_into()?) + sealed::HttpClient::new_https(url.try_into()?, proxy_sign_keypair) } else { - sealed::HttpClient::new_http(url.try_into()?) + sealed::HttpClient::new_http(url.try_into()?, proxy_sign_keypair) }, }) } #[inline] - pub fn uri(&self) -> http::Uri { self.inner.uri() } + pub fn uri(&self) -> Uri { self.inner.uri() } + + #[inline] + pub fn proxy_sign_keypair(&self) -> &Option { self.inner.proxy_sign_keypair() } } #[async_trait] @@ -370,11 +375,15 @@ impl TryFrom for hyper::Uri { mod sealed { use common::log::debug; + use common::X_AUTH_PAYLOAD; + use http::HeaderValue; use hyper::body::Buf; use hyper::client::connect::Connect; use hyper::client::HttpConnector; use hyper::{header, Uri}; use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; + use mm2_p2p::Keypair; + use proxy_signature::RawMessage; use std::io::Read; use tendermint_rpc::{Error, Response, SimpleRequest}; @@ -392,10 +401,17 @@ mod sealed { pub struct HyperClient { uri: Uri, inner: hyper::Client, + proxy_sign_keypair: Option, } impl HyperClient { - pub fn new(uri: Uri, inner: hyper::Client) -> Self { Self { uri, inner } } + pub fn new(uri: Uri, inner: hyper::Client, proxy_sign_keypair: Option) -> Self { + Self { + uri, + inner, + proxy_sign_keypair, + } + } } impl HyperClient @@ -421,21 +437,41 @@ mod sealed { impl HyperClient { /// Build a request using the given Tendermint RPC request. pub fn build_request(&self, request: R) -> Result, Error> { - let request_body = request.into_json(); + let body_bytes = request.into_json().into_bytes(); + let body_size = body_bytes.len(); let mut request = hyper::Request::builder() .method("POST") .uri(&self.uri) - .body(hyper::Body::from(request_body.into_bytes())) + .body(hyper::Body::from(body_bytes)) .map_err(|e| Error::client_internal(e.to_string()))?; { + let request_uri = request.uri().clone(); let headers = request.headers_mut(); - headers.insert(header::CONTENT_TYPE, "application/json".parse().unwrap()); + headers.insert(header::CONTENT_TYPE, HeaderValue::from_static(common::APPLICATION_JSON)); headers.insert( header::USER_AGENT, format!("tendermint.rs/{}", env!("CARGO_PKG_VERSION")).parse().unwrap(), ); + + if let Some(proxy_sign_keypair) = &self.proxy_sign_keypair { + let proxy_sign = RawMessage::sign( + proxy_sign_keypair, + &request_uri, + body_size, + common::PROXY_REQUEST_EXPIRATION_SEC, + ) + .map_err(|e| Error::client_internal(e.to_string()))?; + + let proxy_sign_serialized = + serde_json::to_string(&proxy_sign).map_err(|e| Error::client_internal(e.to_string()))?; + + let header_value = HeaderValue::from_str(&proxy_sign_serialized) + .map_err(|e| Error::client_internal(e.to_string()))?; + + headers.insert(X_AUTH_PAYLOAD, header_value); + } } Ok(request) @@ -454,10 +490,16 @@ mod sealed { } impl HttpClient { - pub fn new_http(uri: Uri) -> Self { Self::Http(HyperClient::new(uri, hyper::Client::new())) } + pub fn new_http(uri: Uri, proxy_sign_keypair: Option) -> Self { + Self::Http(HyperClient::new(uri, hyper::Client::new(), proxy_sign_keypair)) + } - pub fn new_https(uri: Uri) -> Self { - Self::Https(HyperClient::new(uri, hyper::Client::builder().build(https_connector()))) + pub fn new_https(uri: Uri, proxy_sign_keypair: Option) -> Self { + Self::Https(HyperClient::new( + uri, + hyper::Client::builder().build(https_connector()), + proxy_sign_keypair, + )) } pub async fn perform(&self, request: R) -> Result @@ -476,6 +518,13 @@ mod sealed { HttpClient::Https(client) => client.uri.clone(), } } + + pub fn proxy_sign_keypair(&self) -> &Option { + match self { + HttpClient::Http(client) => &client.proxy_sign_keypair, + HttpClient::Https(client) => &client.proxy_sign_keypair, + } + } } async fn response_to_string(response: hyper::Response) -> Result { diff --git a/mm2src/coins/tendermint/rpc/tendermint_wasm_rpc.rs b/mm2src/coins/tendermint/rpc/tendermint_wasm_rpc.rs index 3909ce2c25..9224ea997f 100644 --- a/mm2src/coins/tendermint/rpc/tendermint_wasm_rpc.rs +++ b/mm2src/coins/tendermint/rpc/tendermint_wasm_rpc.rs @@ -1,4 +1,4 @@ -use common::APPLICATION_JSON; +use common::{APPLICATION_JSON, PROXY_REQUEST_EXPIRATION_SEC, X_AUTH_PAYLOAD}; use cosmrs::tendermint::block::Height; use derive_more::Display; use http::header::{ACCEPT, CONTENT_TYPE}; @@ -6,6 +6,8 @@ use http::uri::InvalidUri; use http::{StatusCode, Uri}; use mm2_net::transport::SlurpError; use mm2_net::wasm::http::FetchRequest; +use mm2_p2p::Keypair; +use proxy_signature::RawMessage; use std::str::FromStr; use tendermint_rpc::endpoint::{abci_info, broadcast}; pub use tendermint_rpc::endpoint::{abci_query::{AbciQuery, Request as AbciRequest}, @@ -20,6 +22,7 @@ use tendermint_rpc::Response; #[derive(Debug, Clone)] pub struct HttpClient { uri: String, + proxy_sign_keypair: Option, } #[derive(Debug, Display)] @@ -35,6 +38,7 @@ impl From for HttpClientInitError { pub enum PerformError { TendermintRpc(TendermintRpcError), Slurp(SlurpError), + Internal(String), #[display(fmt = "Request failed with status code {}, response {}", status_code, response)] StatusCode { status_code: StatusCode, @@ -51,27 +55,43 @@ impl From for PerformError { } impl HttpClient { - pub(crate) fn new(url: &str) -> Result { + pub(crate) fn new(url: &str, proxy_sign_keypair: Option) -> Result { Uri::from_str(url)?; - Ok(HttpClient { uri: url.to_owned() }) + Ok(HttpClient { + uri: url.to_owned(), + proxy_sign_keypair, + }) } #[inline] pub fn uri(&self) -> http::Uri { Uri::from_str(&self.uri).expect("This should never happen.") } + #[inline] + pub fn proxy_sign_keypair(&self) -> &Option { &self.proxy_sign_keypair } + pub(crate) async fn perform(&self, request: R) -> Result where R: SimpleRequest, { - let request_str = request.into_json(); - let (status_code, response_str) = FetchRequest::post(&self.uri) - .cors() - .body_utf8(request_str) - .header(ACCEPT.as_str(), APPLICATION_JSON) - .header(CONTENT_TYPE.as_str(), APPLICATION_JSON) - .request_str() - .await - .map_err(|e| e.into_inner())?; + let body_bytes = request.into_json().into_bytes(); + let body_size = body_bytes.len(); + + let mut req = FetchRequest::post(&self.uri).cors().body_bytes(body_bytes); + req = req.header(ACCEPT.as_str(), APPLICATION_JSON); + req = req.header(CONTENT_TYPE.as_str(), APPLICATION_JSON); + + if let Some(proxy_sign_keypair) = &self.proxy_sign_keypair { + let proxy_sign = RawMessage::sign(proxy_sign_keypair, &self.uri(), body_size, PROXY_REQUEST_EXPIRATION_SEC) + .map_err(|e| PerformError::Internal(e.to_string()))?; + + let proxy_sign_serialized = + serde_json::to_string(&proxy_sign).map_err(|e| PerformError::Internal(e.to_string()))?; + + req = req.header(X_AUTH_PAYLOAD, &proxy_sign_serialized); + } + + let (status_code, response_str) = req.request_str().await.map_err(|e| e.into_inner())?; + if !status_code.is_success() { return Err(PerformError::StatusCode { status_code, @@ -118,7 +138,7 @@ mod tests { #[wasm_bindgen_test] async fn test_get_abci_info() { - let client = HttpClient::new("https://rpc.sentry-02.theta-testnet.polypore.xyz").unwrap(); + let client = HttpClient::new("https://rpc.sentry-02.theta-testnet.polypore.xyz", None).unwrap(); client.abci_info().await.unwrap(); } } diff --git a/mm2src/coins/tendermint/tendermint_balance_events.rs b/mm2src/coins/tendermint/tendermint_balance_events.rs index 1a55dfa768..c512cf8277 100644 --- a/mm2src/coins/tendermint/tendermint_balance_events.rs +++ b/mm2src/coins/tendermint/tendermint_balance_events.rs @@ -1,14 +1,14 @@ use async_trait::async_trait; use common::{executor::{AbortSettings, SpawnAbortable}, - http_uri_to_ws_address, log}; + http_uri_to_ws_address, log, PROXY_REQUEST_EXPIRATION_SEC}; use futures::channel::oneshot::{self, Receiver, Sender}; use futures_util::{SinkExt, StreamExt}; -use jsonrpc_core::MethodCall; use jsonrpc_core::{Id as RpcId, Params as RpcParams, Value as RpcValue, Version as RpcVersion}; use mm2_core::mm_ctx::MmArc; use mm2_event_stream::{behaviour::{EventBehaviour, EventInitStatus}, ErrorEventName, Event, EventName, EventStreamConfiguration}; use mm2_number::BigDecimal; +use proxy_signature::RawMessage; use std::collections::{HashMap, HashSet}; use super::TendermintCoin; @@ -21,15 +21,28 @@ impl EventBehaviour for TendermintCoin { fn error_event_name() -> ErrorEventName { ErrorEventName::CoinBalanceError } async fn handle(self, _interval: f64, tx: oneshot::Sender) { - fn generate_subscription_query(query_filter: String) -> String { + fn generate_subscription_query( + query_filter: String, + proxy_sign_keypair: &Option, + uri: &http::Uri, + ) -> String { let mut params = serde_json::Map::with_capacity(1); params.insert("query".to_owned(), RpcValue::String(query_filter)); - let q = MethodCall { - id: RpcId::Num(0), - jsonrpc: Some(RpcVersion::V2), - method: "subscribe".to_owned(), - params: RpcParams::Map(params), + let mut q = json!({ + "id": RpcId::Num(0), + "jsonrpc": Some(RpcVersion::V2), + "method": "subscribe".to_owned(), + "params": RpcParams::Map(params), + }); + + const BODY_SIZE: usize = 0; + if let Some(proxy_sign_keypair) = proxy_sign_keypair { + if let Ok(proxy_sign) = + RawMessage::sign(proxy_sign_keypair, uri, BODY_SIZE, PROXY_REQUEST_EXPIRATION_SEC) + { + q["proxy_sign"] = serde_json::to_value(proxy_sign).expect("This should never happen"); + } }; serde_json::to_string(&q).expect("This should never happen") @@ -48,30 +61,38 @@ impl EventBehaviour for TendermintCoin { let account_id = self.account_id.to_string(); let mut current_balances: HashMap = HashMap::new(); - let receiver_q = generate_subscription_query(format!("coin_received.receiver = '{}'", account_id)); - let receiver_q = tokio_tungstenite_wasm::Message::Text(receiver_q); - - let spender_q = generate_subscription_query(format!("coin_spent.spender = '{}'", account_id)); - let spender_q = tokio_tungstenite_wasm::Message::Text(spender_q); - tx.send(EventInitStatus::Success) .expect("Receiver is dropped, which should never happen."); loop { - let node_uri = match self.rpc_client().await { - Ok(client) => client.uri(), + let client = match self.rpc_client().await { + Ok(client) => client, Err(e) => { log::error!("{e}"); continue; }, }; - let socket_address = format!("{}/{}", http_uri_to_ws_address(node_uri), "websocket"); + let receiver_q = generate_subscription_query( + format!("coin_received.receiver = '{}'", account_id), + client.proxy_sign_keypair(), + &client.uri(), + ); + let receiver_q = tokio_tungstenite_wasm::Message::Text(receiver_q); + + let spender_q = generate_subscription_query( + format!("coin_spent.spender = '{}'", account_id), + client.proxy_sign_keypair(), + &client.uri(), + ); + let spender_q = tokio_tungstenite_wasm::Message::Text(spender_q); + + let socket_address = format!("{}/{}", http_uri_to_ws_address(client.uri()), "websocket"); - let mut wsocket = match tokio_tungstenite_wasm::connect(socket_address).await { + let mut wsocket = match tokio_tungstenite_wasm::connect(&socket_address).await { Ok(ws) => ws, Err(e) => { - log::error!("{e}"); + log::error!("Couldn't connect to '{socket_address}': {e}"); continue; }, }; diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index f073d31d29..323637599d 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -66,8 +66,10 @@ use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; use mm2_git::{FileMetadata, GitController, GithubClient, RepositoryOperations, GITHUB_API_URI}; use mm2_number::MmNumber; +use mm2_p2p::p2p_ctx::P2PContext; use parking_lot::Mutex as PaMutex; use primitives::hash::H256; +use regex::Regex; use rpc::v1::types::Bytes as BytesJson; use serde_json::{self as json, Value as Json}; use std::collections::HashMap; @@ -105,7 +107,16 @@ pub(crate) const TX_DEFAULT_MEMO: &str = ""; const MAX_TIME_LOCK: i64 = 34560; const MIN_TIME_LOCK: i64 = 50; -const ACCOUNT_SEQUENCE_ERR: &str = "incorrect account sequence"; +const ACCOUNT_SEQUENCE_ERR: &str = "account sequence mismatch"; + +lazy_static! { + static ref SEQUENCE_PARSER_REGEX: Regex = Regex::new(r"expected (\d+)").unwrap(); +} + +pub struct SerializedUnsignedTx { + tx_json: Json, + body_bytes: Vec, +} type TendermintPrivKeyPolicy = PrivKeyPolicy; @@ -123,6 +134,23 @@ impl TendermintKeyPair { } } +#[derive(Clone, Deserialize)] +pub struct RpcNode { + url: String, + #[serde(default)] + komodo_proxy: bool, +} + +impl RpcNode { + #[cfg(test)] + fn for_test(url: &str) -> Self { + Self { + url: url.to_string(), + komodo_proxy: false, + } + } +} + #[async_trait] pub trait TendermintCommons { fn platform_denom(&self) -> &Denom; @@ -346,6 +374,7 @@ pub struct TendermintCoinImpl { client: TendermintRpcClient, pub(crate) chain_registry_name: Option, pub(crate) ctx: MmWeak, + pub(crate) is_keplr_from_ledger: bool, } #[derive(Clone)] @@ -599,16 +628,18 @@ impl TendermintCommons for TendermintCoin { } impl TendermintCoin { + #[allow(clippy::too_many_arguments)] pub async fn init( ctx: &MmArc, ticker: String, conf: TendermintConf, protocol_info: TendermintProtocolInfo, - rpc_urls: Vec, + nodes: Vec, tx_history: bool, activation_policy: TendermintActivationPolicy, + is_keplr_from_ledger: bool, ) -> MmResult { - if rpc_urls.is_empty() { + if nodes.is_empty() { return MmError::err(TendermintInitError { ticker, kind: TendermintInitErrorKind::EmptyRpcUrls, @@ -622,7 +653,7 @@ impl TendermintCoin { kind: TendermintInitErrorKind::CouldNotGenerateAccountId(e.to_string()), })?; - let rpc_clients = clients_from_urls(rpc_urls.as_ref()).mm_err(|kind| TendermintInitError { + let rpc_clients = clients_from_urls(ctx, nodes).mm_err(|kind| TendermintInitError { ticker: ticker.clone(), kind, })?; @@ -671,6 +702,7 @@ impl TendermintCoin { client: TendermintRpcClient(AsyncMutex::new(client_impl)), chain_registry_name: protocol_info.chain_registry_name, ctx: ctx.weak(), + is_keplr_from_ledger, }))) } @@ -739,7 +771,7 @@ impl TendermintCoin { // Therefore, we can call SimulateRequest or CheckTx(doesn't work with using Abci interface) to get used gas or fee itself. pub(super) fn gen_simulated_tx( &self, - account_info: BaseAccount, + account_info: &BaseAccount, priv_key: &Secp256k1Secret, tx_payload: Any, timeout_height: u64, @@ -826,10 +858,11 @@ impl TendermintCoin { timeout_height: u64, memo: String, ) -> Result<(String, Raw), TransactionErr> { + let mut account_info = try_tx_s!(self.account_info(&self.account_id).await); let (tx_id, tx_raw) = loop { let tx_raw = try_tx_s!(self.any_to_signed_raw_tx( try_tx_s!(self.activation_policy.activated_key_or_err()), - try_tx_s!(self.account_info(&self.account_id).await), + &account_info, tx_payload.clone(), fee.clone(), timeout_height, @@ -840,6 +873,7 @@ impl TendermintCoin { Ok(tx_id) => break (tx_id, tx_raw), Err(e) => { if e.contains(ACCOUNT_SEQUENCE_ERR) { + account_info.sequence = try_tx_s!(parse_expected_sequence_number(&e)); debug!("Got wrong account sequence, trying again."); continue; } @@ -868,19 +902,14 @@ impl TendermintCoin { let ctx = try_tx_s!(MmArc::from_weak(&self.ctx).ok_or(ERRL!("ctx must be initialized already"))); let account_info = try_tx_s!(self.account_info(&self.account_id).await); - let sign_doc = try_tx_s!(self.any_to_sign_doc(account_info, tx_payload, fee, timeout_height, memo)); - - let unsigned_tx = json!({ - "sign_doc": { - "body_bytes": sign_doc.body_bytes, - "auth_info_bytes": sign_doc.auth_info_bytes, - "chain_id": sign_doc.chain_id, - "account_number": sign_doc.account_number, - } - }); + let SerializedUnsignedTx { tx_json, body_bytes } = if self.is_keplr_from_ledger { + try_tx_s!(self.any_to_legacy_amino_json(&account_info, tx_payload, fee, timeout_height, memo)) + } else { + try_tx_s!(self.any_to_serialized_sign_doc(&account_info, tx_payload, fee, timeout_height, memo)) + }; let data: TxHashData = try_tx_s!(ctx - .ask_for_data(&format!("TX_HASH:{}", self.ticker()), unsigned_tx, timeout) + .ask_for_data(&format!("TX_HASH:{}", self.ticker()), tx_json, timeout) .await .map_err(|e| ERRL!("{}", e))); @@ -892,7 +921,7 @@ impl TendermintCoin { signatures: tx.signatures, }; - if sign_doc.body_bytes != tx_raw_inner.body_bytes { + if body_bytes != tx_raw_inner.body_bytes { return Err(crate::TransactionErr::Plain(ERRL!( "Unsigned transaction don't match with the externally provided transaction." ))); @@ -921,11 +950,11 @@ impl TendermintCoin { return Ok(Fee::from_amount_and_gas(fee_amount, gas_limit)); }; + let mut account_info = self.account_info(&self.account_id).await?; let (response, raw_response) = loop { - let account_info = self.account_info(&self.account_id).await?; let tx_bytes = self .gen_simulated_tx( - account_info, + &account_info, activated_priv_key, msg.clone(), timeout_height, @@ -942,7 +971,9 @@ impl TendermintCoin { let raw_response = self.rpc_client().await?.perform(request).await?; - if raw_response.response.log.to_string().contains(ACCOUNT_SEQUENCE_ERR) { + let log = raw_response.response.log.to_string(); + if log.contains(ACCOUNT_SEQUENCE_ERR) { + account_info.sequence = parse_expected_sequence_number(&log)?; debug!("Got wrong account sequence, trying again."); continue; } @@ -997,10 +1028,10 @@ impl TendermintCoin { return Ok(((GAS_WANTED_BASE_VALUE * 1.5) * gas_price).ceil() as u64); }; + let mut account_info = self.account_info(account_id).await?; let (response, raw_response) = loop { - let account_info = self.account_info(account_id).await?; let tx_bytes = self - .gen_simulated_tx(account_info, &priv_key, msg.clone(), timeout_height, memo.clone()) + .gen_simulated_tx(&account_info, &priv_key, msg.clone(), timeout_height, memo.clone()) .map_to_mm(|e| TendermintCoinRpcError::InternalError(format!("{}", e)))?; let request = AbciRequest::new( @@ -1012,7 +1043,9 @@ impl TendermintCoin { let raw_response = self.rpc_client().await?.perform(request).await?; - if raw_response.response.log.to_string().contains(ACCOUNT_SEQUENCE_ERR) { + let log = raw_response.response.log.to_string(); + if log.contains(ACCOUNT_SEQUENCE_ERR) { + account_info.sequence = parse_expected_sequence_number(&log)?; debug!("Got wrong account sequence, trying again."); continue; } @@ -1151,7 +1184,7 @@ impl TendermintCoin { &self, maybe_pk: Option, message: Any, - account_info: BaseAccount, + account_info: &BaseAccount, fee: Fee, timeout_height: u64, memo: String, @@ -1166,18 +1199,13 @@ impl TendermintCoin { hex::encode_upper(hash.as_slice()), )) } else { - let sign_doc = self.any_to_sign_doc(account_info, message, fee, timeout_height, memo)?; - - let tx = json!({ - "sign_doc": { - "body_bytes": sign_doc.body_bytes, - "auth_info_bytes": sign_doc.auth_info_bytes, - "chain_id": sign_doc.chain_id, - "account_number": sign_doc.account_number, - } - }); + let SerializedUnsignedTx { tx_json, .. } = if self.is_keplr_from_ledger { + self.any_to_legacy_amino_json(account_info, message, fee, timeout_height, memo) + } else { + self.any_to_serialized_sign_doc(account_info, message, fee, timeout_height, memo) + }?; - Ok(TransactionData::Unsigned(tx)) + Ok(TransactionData::Unsigned(tx_json)) } } @@ -1240,7 +1268,7 @@ impl TendermintCoin { pub(super) fn any_to_signed_raw_tx( &self, priv_key: &Secp256k1Secret, - account_info: BaseAccount, + account_info: &BaseAccount, tx_payload: Any, fee: Fee, timeout_height: u64, @@ -1253,18 +1281,116 @@ impl TendermintCoin { sign_doc.sign(&signkey) } - pub(super) fn any_to_sign_doc( + pub(super) fn any_to_serialized_sign_doc( &self, - account_info: BaseAccount, + account_info: &BaseAccount, tx_payload: Any, fee: Fee, timeout_height: u64, memo: String, - ) -> cosmrs::Result { + ) -> cosmrs::Result { let tx_body = tx::Body::new(vec![tx_payload], memo, timeout_height as u32); let pubkey = self.activation_policy.public_key()?.into(); let auth_info = SignerInfo::single_direct(Some(pubkey), account_info.sequence).auth_info(fee); - SignDoc::new(&tx_body, &auth_info, &self.chain_id, account_info.account_number) + let sign_doc = SignDoc::new(&tx_body, &auth_info, &self.chain_id, account_info.account_number)?; + + let tx_json = json!({ + "sign_doc": { + "body_bytes": sign_doc.body_bytes, + "auth_info_bytes": sign_doc.auth_info_bytes, + "chain_id": sign_doc.chain_id, + "account_number": sign_doc.account_number, + } + }); + + Ok(SerializedUnsignedTx { + tx_json, + body_bytes: sign_doc.body_bytes, + }) + } + + /// This should only be used for Keplr from Ledger! + /// When using Keplr from Ledger, they don't accept `SING_MODE_DIRECT` transactions. + /// + /// Visit https://docs.cosmos.network/main/build/architecture/adr-050-sign-mode-textual#context for more context. + pub(super) fn any_to_legacy_amino_json( + &self, + account_info: &BaseAccount, + tx_payload: Any, + fee: Fee, + timeout_height: u64, + memo: String, + ) -> cosmrs::Result { + const MSG_SEND_TYPE_URL: &str = "/cosmos.bank.v1beta1.MsgSend"; + const LEDGER_MSG_SEND_TYPE_URL: &str = "cosmos-sdk/MsgSend"; + + // Ledger's keplr works as wallet-only, so `MsgSend` support is enough for now. + if tx_payload.type_url != MSG_SEND_TYPE_URL { + return Err(ErrorReport::new(io::Error::new( + io::ErrorKind::Unsupported, + format!( + "Signing mode `SIGN_MODE_LEGACY_AMINO_JSON` is not supported for '{}' transaction type.", + tx_payload.type_url + ), + ))); + } + + let msg_send = MsgSend::from_any(&tx_payload)?; + let timeout_height = u32::try_from(timeout_height)?; + let original_tx_type_url = tx_payload.type_url.clone(); + let body_bytes = tx::Body::new(vec![tx_payload], &memo, timeout_height).into_bytes()?; + + let amount: Vec = msg_send + .amount + .into_iter() + .map(|t| { + json!( { + "denom": t.denom, + // Numbers needs to be converted into string type. + // Ref: https://github.com/cosmos/ledger-cosmos/blob/c707129e59f6e0f07ad67161a6b75e8951af063c/docs/TXSPEC.md#json-format + "amount": t.amount.to_string(), + }) + }) + .collect(); + + let msg = json!({ + "type": LEDGER_MSG_SEND_TYPE_URL, + "value": json!({ + "from_address": msg_send.from_address.to_string(), + "to_address": msg_send.to_address.to_string(), + "amount": amount, + }) + }); + + let fee_amount: Vec = fee + .amount + .into_iter() + .map(|t| { + json!( { + "denom": t.denom, + // Numbers needs to be converted into string type. + // Ref: https://github.com/cosmos/ledger-cosmos/blob/c707129e59f6e0f07ad67161a6b75e8951af063c/docs/TXSPEC.md#json-format + "amount": t.amount.to_string(), + }) + }) + .collect(); + + let tx_json = serde_json::json!({ + "legacy_amino_json": { + "account_number": account_info.account_number.to_string(), + "chain_id": self.chain_id.to_string(), + "fee": { + "amount": fee_amount, + "gas": fee.gas_limit.to_string() + }, + "memo": memo, + "msgs": [msg], + "sequence": account_info.sequence.to_string(), + }, + "original_tx_type_url": original_tx_type_url, + }); + + Ok(SerializedUnsignedTx { tx_json, body_bytes }) } pub fn add_activated_token_info(&self, ticker: String, decimals: u8, denom: Denom) { @@ -1956,18 +2082,28 @@ impl TendermintCoin { } } -fn clients_from_urls(rpc_urls: &[String]) -> MmResult, TendermintInitErrorKind> { - if rpc_urls.is_empty() { +fn clients_from_urls(ctx: &MmArc, nodes: Vec) -> MmResult, TendermintInitErrorKind> { + if nodes.is_empty() { return MmError::err(TendermintInitErrorKind::EmptyRpcUrls); } + + let p2p_keypair = if nodes.iter().any(|n| n.komodo_proxy) { + let p2p_ctx = P2PContext::fetch_from_mm_arc(ctx); + Some(p2p_ctx.keypair().clone()) + } else { + None + }; + let mut clients = Vec::new(); let mut errors = Vec::new(); + // check that all urls are valid // keep all invalid urls in one vector to show all of them in error - for url in rpc_urls.iter() { - match HttpClient::new(url.as_str()) { + for node in nodes.iter() { + let proxy_sign_keypair = if node.komodo_proxy { p2p_keypair.clone() } else { None }; + match HttpClient::new(node.url.as_str(), proxy_sign_keypair) { Ok(client) => clients.push(client), - Err(e) => errors.push(format!("Url {} is invalid, got error {}", url, e)), + Err(e) => errors.push(format!("Url {} is invalid, got error {}", node.url, e)), } } drop_mutability!(clients); @@ -2024,6 +2160,17 @@ pub async fn get_ibc_chain_list() -> IBCChainRegistriesResult { impl MmCoin for TendermintCoin { fn is_asset_chain(&self) -> bool { false } + fn wallet_only(&self, ctx: &MmArc) -> bool { + let coin_conf = crate::coin_conf(ctx, self.ticker()); + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if coin_conf.is_null() { + return true; + } + let wallet_only_conf = coin_conf["wallet_only"].as_bool().unwrap_or(false); + + wallet_only_conf || self.is_keplr_from_ledger + } + fn spawner(&self) -> CoinFutSpawner { CoinFutSpawner::new(&self.abortable_system) } fn withdraw(&self, req: WithdrawRequest) -> WithdrawFut { @@ -2060,29 +2207,23 @@ impl MmCoin for TendermintCoin { BigDecimal::default() }; - let msg_payload = if is_ibc_transfer { - let channel_id = match req.ibc_source_channel { - Some(channel_id) => channel_id, - None => coin.detect_channel_id_for_ibc_transfer(&to_address).await?, - }; - - MsgTransfer::new_with_default_timeout(channel_id, account_id.clone(), to_address.clone(), Coin { - denom: coin.denom.clone(), - amount: amount_denom.into(), - }) - .to_any() - } else { - MsgSend { - from_address: account_id.clone(), - to_address: to_address.clone(), - amount: vec![Coin { - denom: coin.denom.clone(), - amount: amount_denom.into(), - }], + let channel_id = if is_ibc_transfer { + match &req.ibc_source_channel { + Some(_) => req.ibc_source_channel, + None => Some(coin.detect_channel_id_for_ibc_transfer(&to_address).await?), } - .to_any() - } - .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + } else { + None + }; + + let msg_payload = create_withdraw_msg_as_any( + account_id.clone(), + to_address.clone(), + &coin.denom, + amount_denom, + channel_id.clone(), + ) + .await?; let memo = req.memo.unwrap_or_else(|| TX_DEFAULT_MEMO.into()); @@ -2110,6 +2251,18 @@ impl MmCoin for TendermintCoin { req.fee, ) .await?; + + let fee_amount_u64 = if coin.is_keplr_from_ledger { + // When using `SIGN_MODE_LEGACY_AMINO_JSON`, Keplr ignores the fee we calculated + // and calculates another one which is usually double what we calculate. + // To make sure the transaction doesn't fail on the Keplr side (because if Keplr + // calculates a higher fee than us, the withdrawal might fail), we use three times + // the actual fee. + fee_amount_u64 * 3 + } else { + fee_amount_u64 + }; + let fee_amount_dec = big_decimal_from_sat_unsigned(fee_amount_u64, coin.decimals()); let fee_amount = Coin { @@ -2142,10 +2295,19 @@ impl MmCoin for TendermintCoin { (sat_from_big_decimal(&req.amount, coin.decimals)?, total) }; + let msg_payload = create_withdraw_msg_as_any( + account_id.clone(), + to_address.clone(), + &coin.denom, + amount_denom, + channel_id, + ) + .await?; + let account_info = coin.account_info(&account_id).await?; let tx = coin - .any_to_transaction_data(maybe_pk, msg_payload, account_info, fee, timeout_height, memo.clone()) + .any_to_transaction_data(maybe_pk, msg_payload, &account_info, fee, timeout_height, memo.clone()) .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; let internal_id = { @@ -2373,13 +2535,13 @@ impl MarketCoinOps for TendermintCoin { let broadcast_res = try_s!(try_s!(coin.rpc_client().await).broadcast_tx_commit(tx_bytes).await); if broadcast_res.check_tx.log.contains(ACCOUNT_SEQUENCE_ERR) - || broadcast_res.deliver_tx.log.contains(ACCOUNT_SEQUENCE_ERR) + || broadcast_res.tx_result.log.contains(ACCOUNT_SEQUENCE_ERR) { return ERR!( "{}. check_tx log: {}, deliver_tx log: {}", ACCOUNT_SEQUENCE_ERR, broadcast_res.check_tx.log, - broadcast_res.deliver_tx.log + broadcast_res.tx_result.log ); } @@ -2387,8 +2549,8 @@ impl MarketCoinOps for TendermintCoin { return ERR!("Tx check failed {:?}", broadcast_res.check_tx); } - if !broadcast_res.deliver_tx.code.is_ok() { - return ERR!("Tx deliver failed {:?}", broadcast_res.deliver_tx); + if !broadcast_res.tx_result.code.is_ok() { + return ERR!("Tx deliver failed {:?}", broadcast_res.tx_result); } Ok(broadcast_res.hash.to_string()) }; @@ -2438,14 +2600,14 @@ impl MarketCoinOps for TendermintCoin { Box::new(fut.boxed().compat()) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { - let tx = try_tx_fus!(cosmrs::Tx::from_bytes(args.tx_bytes)); - let first_message = try_tx_fus!(tx.body.messages.first().ok_or("Tx body couldn't be read.")); - let htlc_proto = try_tx_fus!(CreateHtlcProto::decode( - try_tx_fus!(HtlcType::from_str(&self.account_prefix)), + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { + let tx = try_tx_s!(cosmrs::Tx::from_bytes(args.tx_bytes)); + let first_message = try_tx_s!(tx.body.messages.first().ok_or("Tx body couldn't be read.")); + let htlc_proto = try_tx_s!(CreateHtlcProto::decode( + try_tx_s!(HtlcType::from_str(&self.account_prefix)), first_message.value.as_slice() )); - let htlc = try_tx_fus!(CreateHtlcMsg::try_from(htlc_proto)); + let htlc = try_tx_s!(CreateHtlcMsg::try_from(htlc_proto)); let htlc_id = self.calculate_htlc_id(htlc.sender(), htlc.to(), htlc.amount(), args.secret_hash); let events_string = format!("claim_htlc.id='{}'", htlc_id); @@ -2460,38 +2622,32 @@ impl MarketCoinOps for TendermintCoin { }; let encoded_request = request.encode_to_vec(); - let coin = self.clone(); - let wait_until = args.wait_until; - let fut = async move { - loop { - let response = try_tx_s!( - try_tx_s!(coin.rpc_client().await) - .abci_query( - Some(ABCI_GET_TXS_EVENT_PATH.to_string()), - encoded_request.as_slice(), - ABCI_REQUEST_HEIGHT, - ABCI_REQUEST_PROVE - ) - .await - ); - let response = try_tx_s!(GetTxsEventResponse::decode(response.value.as_slice())); - if let Some(tx) = response.txs.first() { - return Ok(TransactionEnum::CosmosTransaction(CosmosTransaction { - data: TxRaw { - body_bytes: tx.body.as_ref().map(Message::encode_to_vec).unwrap_or_default(), - auth_info_bytes: tx.auth_info.as_ref().map(Message::encode_to_vec).unwrap_or_default(), - signatures: tx.signatures.clone(), - }, - })); - } - Timer::sleep(5.).await; - if get_utc_timestamp() > wait_until as i64 { - return Err(TransactionErr::Plain("Waited too long".into())); - } + loop { + let response = try_tx_s!( + try_tx_s!(self.rpc_client().await) + .abci_query( + Some(ABCI_GET_TXS_EVENT_PATH.to_string()), + encoded_request.as_slice(), + ABCI_REQUEST_HEIGHT, + ABCI_REQUEST_PROVE + ) + .await + ); + let response = try_tx_s!(GetTxsEventResponse::decode(response.value.as_slice())); + if let Some(tx) = response.txs.first() { + return Ok(TransactionEnum::CosmosTransaction(CosmosTransaction { + data: TxRaw { + body_bytes: tx.body.as_ref().map(Message::encode_to_vec).unwrap_or_default(), + auth_info_bytes: tx.auth_info.as_ref().map(Message::encode_to_vec).unwrap_or_default(), + signatures: tx.signatures.clone(), + }, + })); } - }; - - Box::new(fut.boxed().compat()) + Timer::sleep(5.).await; + if get_utc_timestamp() > args.wait_until as i64 { + return Err(TransactionErr::Plain("Waited too long".into())); + } + } } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -2533,7 +2689,7 @@ impl MarketCoinOps for TendermintCoin { #[async_trait] #[allow(unused_variables)] impl SwapOps for TendermintCoin { - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionFut { + async fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionResult { self.send_taker_fee_for_denom( fee_addr, dex_fee.fee_amount().into(), @@ -2542,9 +2698,11 @@ impl SwapOps for TendermintCoin { uuid, expire_at, ) + .compat() + .await } - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { self.send_htlc_for_denom( maker_payment_args.time_lock_duration, maker_payment_args.other_pubkey, @@ -2553,9 +2711,11 @@ impl SwapOps for TendermintCoin { self.denom.clone(), self.decimals, ) + .compat() + .await } - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { self.send_htlc_for_denom( taker_payment_args.time_lock_duration, taker_payment_args.other_pubkey, @@ -2564,6 +2724,8 @@ impl SwapOps for TendermintCoin { self.denom.clone(), self.decimals, ) + .compat() + .await } async fn send_maker_spends_taker_payment( @@ -2700,7 +2862,7 @@ impl SwapOps for TendermintCoin { )) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { self.validate_fee_for_denom( validate_fee_args.fee_tx, validate_fee_args.expected_sender, @@ -2710,6 +2872,8 @@ impl SwapOps for TendermintCoin { validate_fee_args.uuid, self.denom.to_string(), ) + .compat() + .await } async fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { @@ -2722,10 +2886,10 @@ impl SwapOps for TendermintCoin { .await } - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { self.check_if_my_payment_sent_for_denom( self.decimals, self.denom.clone(), @@ -2733,6 +2897,8 @@ impl SwapOps for TendermintCoin { if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.amount, ) + .compat() + .await } async fn search_for_swap_tx_spend_my( @@ -3030,6 +3196,33 @@ pub(crate) fn chain_registry_name_from_account_prefix(ctx: &MmArc, prefix: &str) None } +pub(crate) async fn create_withdraw_msg_as_any( + sender: AccountId, + receiver: AccountId, + denom: &Denom, + amount: u64, + ibc_source_channel: Option, +) -> Result> { + if let Some(channel_id) = ibc_source_channel { + MsgTransfer::new_with_default_timeout(channel_id, sender, receiver, Coin { + denom: denom.clone(), + amount: amount.into(), + }) + .to_any() + } else { + MsgSend { + from_address: sender, + to_address: receiver, + amount: vec![Coin { + denom: denom.clone(), + amount: amount.into(), + }], + } + .to_any() + } + .map_to_mm(|e| WithdrawError::InternalError(e.to_string())) +} + pub async fn get_ibc_transfer_channels( source_registry_name: String, destination_registry_name: String, @@ -3110,6 +3303,20 @@ pub async fn get_ibc_transfer_channels( }) } +fn parse_expected_sequence_number(e: &str) -> MmResult { + if let Some(sequence) = SEQUENCE_PARSER_REGEX.captures(e).and_then(|c| c.get(1)) { + let account_sequence = + u64::from_str(sequence.as_str()).map_to_mm(|e| TendermintCoinRpcError::InternalError(e.to_string()))?; + + return Ok(account_sequence); + } + + MmError::err(TendermintCoinRpcError::InternalError(format!( + "Could not parse the expected sequence number from this error message: '{}'", + e + ))) +} + #[cfg(test)] pub mod tendermint_coin_tests { use super::*; @@ -3190,7 +3397,7 @@ pub mod tendermint_coin_tests { #[test] fn test_htlc_create_and_claim() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_protocol(); @@ -3211,9 +3418,10 @@ pub mod tendermint_coin_tests { "IRIS".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, + false, )) .unwrap(); @@ -3258,26 +3466,23 @@ pub mod tendermint_coin_tests { fee, timeout_height, TX_DEFAULT_MEMO.into(), - Duration::from_secs(10), + Duration::from_secs(20), ); block_on(async { send_tx_fut.await.unwrap(); }); // >> END HTLC CREATION - let htlc_spent = block_on( - coin.check_if_my_payment_sent(CheckIfMyPaymentSentArgs { - time_lock: 0, - other_pub: IRIS_TESTNET_HTLC_PAIR2_PUB_KEY, - secret_hash: sha256(&sec).as_slice(), - search_from_block: current_block, - swap_contract_address: &None, - swap_unique_data: &[], - amount: &amount_dec, - payment_instructions: &None, - }) - .compat(), - ) + let htlc_spent = block_on(coin.check_if_my_payment_sent(CheckIfMyPaymentSentArgs { + time_lock: 0, + other_pub: IRIS_TESTNET_HTLC_PAIR2_PUB_KEY, + secret_hash: sha256(&sec).as_slice(), + search_from_block: current_block, + swap_contract_address: &None, + swap_unique_data: &[], + amount: &amount_dec, + payment_instructions: &None, + })) .unwrap(); assert!(htlc_spent.is_some()); @@ -3315,7 +3520,7 @@ pub mod tendermint_coin_tests { #[test] fn try_query_claim_htlc_txs_and_get_secret() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_usdc_ibc_protocol(); @@ -3336,9 +3541,10 @@ pub mod tendermint_coin_tests { "USDC-IBC".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, + false, )) .unwrap(); @@ -3377,7 +3583,7 @@ pub mod tendermint_coin_tests { #[test] fn wait_for_tx_spend_test() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_usdc_ibc_protocol(); @@ -3398,9 +3604,10 @@ pub mod tendermint_coin_tests { "USDC-IBC".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, + false, )) .unwrap(); @@ -3428,18 +3635,15 @@ pub mod tendermint_coin_tests { let encoded_tx = tx.encode_to_vec(); let secret_hash = hex::decode("0C34C71EBA2A51738699F9F3D6DAFFB15BE576E8ED543203485791B5DA39D10D").unwrap(); - let spend_tx = block_on( - coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &encoded_tx, - secret_hash: &secret_hash, - wait_until: get_utc_timestamp() as u64, - from_block: 0, - swap_contract_address: &None, - check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: false, - }) - .compat(), - ) + let spend_tx = block_on(coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &encoded_tx, + secret_hash: &secret_hash, + wait_until: get_utc_timestamp() as u64, + from_block: 0, + swap_contract_address: &None, + check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, + watcher_reward: false, + })) .unwrap(); // https://nyancat.iobscan.io/#/tx?txHash=565C820C1F95556ADC251F16244AAD4E4274772F41BC13F958C9C2F89A14D137 @@ -3450,7 +3654,7 @@ pub mod tendermint_coin_tests { #[test] fn validate_taker_fee_test() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_protocol(); @@ -3471,9 +3675,10 @@ pub mod tendermint_coin_tests { "IRIS-TEST".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, + false, )) .unwrap(); @@ -3488,18 +3693,16 @@ pub mod tendermint_coin_tests { }); let invalid_amount: MmNumber = 1.into(); - let error = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &create_htlc_tx, - expected_sender: &[], - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(invalid_amount.clone()), - min_block_number: 0, - uuid: &[1; 16], - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &create_htlc_tx, + expected_sender: &[], + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(invalid_amount.clone()), + min_block_number: 0, + uuid: &[1; 16], + })) + .unwrap_err() + .into_inner(); println!("{}", error); match error { ValidatePaymentError::TxDeserializationError(err) => { @@ -3522,18 +3725,16 @@ pub mod tendermint_coin_tests { data: TxRaw::decode(random_transfer_tx_bytes.as_slice()).unwrap(), }); - let error = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &random_transfer_tx, - expected_sender: &[], - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(invalid_amount.clone()), - min_block_number: 0, - uuid: &[1; 16], - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &random_transfer_tx, + expected_sender: &[], + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(invalid_amount.clone()), + min_block_number: 0, + uuid: &[1; 16], + })) + .unwrap_err() + .into_inner(); println!("{}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("sent to wrong address")), @@ -3555,18 +3756,16 @@ pub mod tendermint_coin_tests { data: TxRaw::decode(dex_fee_tx.encode_to_vec().as_slice()).unwrap(), }); - let error = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &dex_fee_tx, - expected_sender: &[], - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(invalid_amount), - min_block_number: 0, - uuid: &[1; 16], - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &dex_fee_tx, + expected_sender: &[], + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(invalid_amount), + min_block_number: 0, + uuid: &[1; 16], + })) + .unwrap_err() + .into_inner(); println!("{}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Invalid amount")), @@ -3575,18 +3774,16 @@ pub mod tendermint_coin_tests { let valid_amount: BigDecimal = "0.0001".parse().unwrap(); // valid amount but invalid sender - let error = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &dex_fee_tx, - expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(valid_amount.clone().into()), - min_block_number: 0, - uuid: &[1; 16], - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &dex_fee_tx, + expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(valid_amount.clone().into()), + min_block_number: 0, + uuid: &[1; 16], + })) + .unwrap_err() + .into_inner(); println!("{}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Invalid sender")), @@ -3594,18 +3791,16 @@ pub mod tendermint_coin_tests { } // invalid memo - let error = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &dex_fee_tx, - expected_sender: &pubkey, - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(valid_amount.into()), - min_block_number: 0, - uuid: &[1; 16], - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &dex_fee_tx, + expected_sender: &pubkey, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(valid_amount.into()), + min_block_number: 0, + uuid: &[1; 16], + })) + .unwrap_err() + .into_inner(); println!("{}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Invalid memo")), @@ -3646,7 +3841,7 @@ pub mod tendermint_coin_tests { #[test] fn validate_payment_test() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_protocol(); @@ -3667,9 +3862,10 @@ pub mod tendermint_coin_tests { "IRIS-TEST".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, + false, )) .unwrap(); @@ -3728,7 +3924,7 @@ pub mod tendermint_coin_tests { #[test] fn test_search_for_swap_tx_spend_spent() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_protocol(); @@ -3749,9 +3945,10 @@ pub mod tendermint_coin_tests { "IRIS-TEST".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, + false, )) .unwrap(); @@ -3803,7 +4000,7 @@ pub mod tendermint_coin_tests { #[test] fn test_search_for_swap_tx_spend_refunded() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_protocol(); @@ -3824,9 +4021,10 @@ pub mod tendermint_coin_tests { "IRIS-TEST".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, + false, )) .unwrap(); @@ -3876,7 +4074,7 @@ pub mod tendermint_coin_tests { #[test] fn test_get_tx_status_code_or_none() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_usdc_ibc_protocol(); let conf = TendermintConf { @@ -3895,9 +4093,10 @@ pub mod tendermint_coin_tests { "USDC-IBC".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, + false, )) .unwrap(); @@ -3930,7 +4129,7 @@ pub mod tendermint_coin_tests { fn test_wait_for_confirmations() { const CHECK_INTERVAL: u64 = 2; - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_usdc_ibc_protocol(); let conf = TendermintConf { @@ -3949,9 +4148,10 @@ pub mod tendermint_coin_tests { "USDC-IBC".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, + false, )) .unwrap(); @@ -4010,4 +4210,19 @@ pub mod tendermint_coin_tests { // Public and private keys are from the same keypair, account ids must be equal. assert_eq!(pk_account_id, pb_account_id); } + + #[test] + fn test_parse_expected_sequence_number() { + assert_eq!( + 13, + parse_expected_sequence_number("check_tx log: account sequence mismatch, expected 13").unwrap() + ); + assert_eq!( + 5, + parse_expected_sequence_number("check_tx log: account sequence mismatch, expected 5, got...").unwrap() + ); + assert_eq!(17, parse_expected_sequence_number("account sequence mismatch, expected. check_tx log: account sequence mismatch, expected 17, got 16: incorrect account sequence, deliver_tx log...").unwrap()); + assert!(parse_expected_sequence_number("").is_err()); + assert!(parse_expected_sequence_number("check_tx log: account sequence mismatch, expected").is_err()); + } } diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 894982aa83..e5cc90f895 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -1,9 +1,8 @@ //! Module containing implementation for Tendermint Tokens. They include native assets + IBC -use super::ibc::transfer_v1::MsgTransfer; use super::ibc::IBC_GAS_LIMIT_DEFAULT; -use super::{TendermintCoin, TendermintFeeDetails, GAS_LIMIT_DEFAULT, MIN_TX_SATOSHIS, TIMEOUT_HEIGHT_DELTA, - TX_DEFAULT_MEMO}; +use super::{create_withdraw_msg_as_any, TendermintCoin, TendermintFeeDetails, GAS_LIMIT_DEFAULT, MIN_TX_SATOSHIS, + TIMEOUT_HEIGHT_DELTA, TX_DEFAULT_MEMO}; use crate::coin_errors::ValidatePaymentResult; use crate::utxo::utxo_common::big_decimal_from_sat; use crate::{big_decimal_from_sat_unsigned, utxo::sat_from_big_decimal, BalanceFut, BigDecimal, @@ -26,9 +25,7 @@ use common::executor::abortable_queue::AbortableQueue; use common::executor::{AbortableSystem, AbortedError}; use common::log::warn; use common::Future01CompatExt; -use cosmrs::{bank::MsgSend, - tx::{Fee, Msg}, - AccountId, Coin, Denom}; +use cosmrs::{tx::Fee, AccountId, Coin, Denom}; use futures::{FutureExt, TryFutureExt}; use futures01::Future; use keys::KeyPair; @@ -107,37 +104,46 @@ impl TendermintToken { #[async_trait] #[allow(unused_variables)] impl SwapOps for TendermintToken { - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionFut { - self.platform_coin.send_taker_fee_for_denom( - fee_addr, - dex_fee.fee_amount().into(), - self.denom.clone(), - self.decimals, - uuid, - expire_at, - ) - } - - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { - self.platform_coin.send_htlc_for_denom( - maker_payment_args.time_lock_duration, - maker_payment_args.other_pubkey, - maker_payment_args.secret_hash, - maker_payment_args.amount, - self.denom.clone(), - self.decimals, - ) - } - - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { - self.platform_coin.send_htlc_for_denom( - taker_payment_args.time_lock_duration, - taker_payment_args.other_pubkey, - taker_payment_args.secret_hash, - taker_payment_args.amount, - self.denom.clone(), - self.decimals, - ) + async fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionResult { + self.platform_coin + .send_taker_fee_for_denom( + fee_addr, + dex_fee.fee_amount().into(), + self.denom.clone(), + self.decimals, + uuid, + expire_at, + ) + .compat() + .await + } + + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + self.platform_coin + .send_htlc_for_denom( + maker_payment_args.time_lock_duration, + maker_payment_args.other_pubkey, + maker_payment_args.secret_hash, + maker_payment_args.amount, + self.denom.clone(), + self.decimals, + ) + .compat() + .await + } + + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + self.platform_coin + .send_htlc_for_denom( + taker_payment_args.time_lock_duration, + taker_payment_args.other_pubkey, + taker_payment_args.secret_hash, + taker_payment_args.amount, + self.denom.clone(), + self.decimals, + ) + .compat() + .await } async fn send_maker_spends_taker_payment( @@ -170,16 +176,19 @@ impl SwapOps for TendermintToken { )) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { - self.platform_coin.validate_fee_for_denom( - validate_fee_args.fee_tx, - validate_fee_args.expected_sender, - validate_fee_args.fee_addr, - &validate_fee_args.dex_fee.fee_amount().into(), - self.decimals, - validate_fee_args.uuid, - self.denom.to_string(), - ) + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { + self.platform_coin + .validate_fee_for_denom( + validate_fee_args.fee_tx, + validate_fee_args.expected_sender, + validate_fee_args.fee_addr, + &validate_fee_args.dex_fee.fee_amount().into(), + self.decimals, + validate_fee_args.uuid, + self.denom.to_string(), + ) + .compat() + .await } async fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { @@ -194,17 +203,20 @@ impl SwapOps for TendermintToken { .await } - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { - self.platform_coin.check_if_my_payment_sent_for_denom( - self.decimals, - self.denom.clone(), - if_my_payment_sent_args.other_pub, - if_my_payment_sent_args.secret_hash, - if_my_payment_sent_args.amount, - ) + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { + self.platform_coin + .check_if_my_payment_sent_for_denom( + self.decimals, + self.denom.clone(), + if_my_payment_sent_args.other_pub, + if_my_payment_sent_args.secret_hash, + if_my_payment_sent_args.amount, + ) + .compat() + .await } async fn search_for_swap_tx_spend_my( @@ -441,16 +453,18 @@ impl MarketCoinOps for TendermintToken { self.platform_coin.wait_for_confirmations(input) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { - self.platform_coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: args.tx_bytes, - secret_hash: args.secret_hash, - wait_until: args.wait_until, - from_block: args.from_block, - swap_contract_address: args.swap_contract_address, - check_every: args.check_every, - watcher_reward: false, - }) + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { + self.platform_coin + .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: args.tx_bytes, + secret_hash: args.secret_hash, + wait_until: args.wait_until, + from_block: args.from_block, + swap_contract_address: args.swap_contract_address, + check_every: args.check_every, + watcher_reward: false, + }) + .await } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -475,6 +489,17 @@ impl MarketCoinOps for TendermintToken { impl MmCoin for TendermintToken { fn is_asset_chain(&self) -> bool { false } + fn wallet_only(&self, ctx: &MmArc) -> bool { + let coin_conf = crate::coin_conf(ctx, self.ticker()); + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if coin_conf.is_null() { + return true; + } + let wallet_only_conf = coin_conf["wallet_only"].as_bool().unwrap_or(false); + + wallet_only_conf || self.platform_coin.is_keplr_from_ledger + } + fn spawner(&self) -> CoinFutSpawner { CoinFutSpawner::new(&self.abortable_system) } fn withdraw(&self, req: WithdrawRequest) -> WithdrawFut { @@ -531,29 +556,23 @@ impl MmCoin for TendermintToken { BigDecimal::default() }; - let msg_payload = if is_ibc_transfer { - let channel_id = match req.ibc_source_channel { - Some(channel_id) => channel_id, - None => platform.detect_channel_id_for_ibc_transfer(&to_address).await?, - }; - - MsgTransfer::new_with_default_timeout(channel_id, account_id.clone(), to_address.clone(), Coin { - denom: token.denom.clone(), - amount: amount_denom.into(), - }) - .to_any() - } else { - MsgSend { - from_address: account_id.clone(), - to_address: to_address.clone(), - amount: vec![Coin { - denom: token.denom.clone(), - amount: amount_denom.into(), - }], + let channel_id = if is_ibc_transfer { + match &req.ibc_source_channel { + Some(_) => req.ibc_source_channel, + None => Some(platform.detect_channel_id_for_ibc_transfer(&to_address).await?), } - .to_any() - } - .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + } else { + None + }; + + let msg_payload = create_withdraw_msg_as_any( + account_id.clone(), + to_address.clone(), + &token.denom, + amount_denom, + channel_id.clone(), + ) + .await?; let memo = req.memo.unwrap_or_else(|| TX_DEFAULT_MEMO.into()); let current_block = token @@ -601,7 +620,7 @@ impl MmCoin for TendermintToken { let account_info = platform.account_info(&account_id).await?; let tx = platform - .any_to_transaction_data(maybe_pk, msg_payload, account_info, fee, timeout_height, memo.clone()) + .any_to_transaction_data(maybe_pk, msg_payload, &account_info, fee, timeout_height, memo.clone()) .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; let internal_id = { diff --git a/mm2src/coins/tendermint/tendermint_tx_history_v2.rs b/mm2src/coins/tendermint/tendermint_tx_history_v2.rs index c9abaaa398..3dc95e7443 100644 --- a/mm2src/coins/tendermint/tendermint_tx_history_v2.rs +++ b/mm2src/coins/tendermint/tendermint_tx_history_v2.rs @@ -8,11 +8,12 @@ use crate::utxo::utxo_common::big_decimal_from_sat_unsigned; use crate::{HistorySyncState, MarketCoinOps, MmCoin, TransactionData, TransactionDetails, TransactionType, TxFeeDetails}; use async_trait::async_trait; +use base64::Engine; use bitcrypto::sha256; use common::executor::Timer; use common::log; -use cosmrs::tendermint::abci::Code as TxCode; use cosmrs::tendermint::abci::Event; +use cosmrs::tendermint::abci::{Code as TxCode, EventAttribute}; use cosmrs::tx::Fee; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::MmResult; @@ -24,6 +25,40 @@ use rpc::v1::types::Bytes as BytesJson; use std::cmp; use std::convert::Infallible; +const TX_PAGE_SIZE: u8 = 50; + +const DEFAULT_TRANSFER_EVENT_COUNT: usize = 1; + +const TRANSFER_EVENT: &str = "transfer"; + +const CREATE_HTLC_EVENT: &str = "create_htlc"; +const CLAIM_HTLC_EVENT: &str = "claim_htlc"; + +const IBC_SEND_EVENT: &str = "ibc_transfer"; +const IBC_RECEIVE_EVENT: &str = "fungible_token_packet"; +const IBC_NFT_RECEIVE_EVENT: &str = "non_fungible_token_packet"; + +const ACCEPTED_EVENTS: &[&str] = &[ + TRANSFER_EVENT, + CREATE_HTLC_EVENT, + CLAIM_HTLC_EVENT, + IBC_SEND_EVENT, + IBC_RECEIVE_EVENT, + IBC_NFT_RECEIVE_EVENT, +]; + +const RECEIVER_TAG_KEY: &str = "receiver"; +const RECEIVER_TAG_KEY_BASE64: &str = "cmVjZWl2ZXI="; + +const RECIPIENT_TAG_KEY: &str = "recipient"; +const RECIPIENT_TAG_KEY_BASE64: &str = "cmVjaXBpZW50"; + +const SENDER_TAG_KEY: &str = "sender"; +const SENDER_TAG_KEY_BASE64: &str = "c2VuZGVy"; + +const AMOUNT_TAG_KEY: &str = "amount"; +const AMOUNT_TAG_KEY_BASE64: &str = "YW1vdW50"; + macro_rules! try_or_return_stopped_as_err { ($exp:expr, $reason: expr, $fmt:literal) => { match $exp { @@ -294,18 +329,6 @@ where self: Box, ctx: &mut TendermintTxHistoryStateMachine, ) -> StateResult> { - const TX_PAGE_SIZE: u8 = 50; - - const DEFAULT_TRANSFER_EVENT_COUNT: usize = 1; - const CREATE_HTLC_EVENT: &str = "create_htlc"; - const CLAIM_HTLC_EVENT: &str = "claim_htlc"; - const TRANSFER_EVENT: &str = "transfer"; - const ACCEPTED_EVENTS: &[&str] = &[CREATE_HTLC_EVENT, CLAIM_HTLC_EVENT, TRANSFER_EVENT]; - const RECIPIENT_TAG_KEY: &str = "recipient"; - const SENDER_TAG_KEY: &str = "sender"; - const RECEIVER_TAG_KEY: &str = "receiver"; - const AMOUNT_TAG_KEY: &str = "amount"; - struct TxAmounts { total: BigDecimal, spent_by_me: BigDecimal, @@ -378,6 +401,8 @@ where Standard, CreateHtlc, ClaimHtlc, + IBCSend, + IBCReceive, } #[derive(Clone)] @@ -389,43 +414,69 @@ where transfer_event_type: TransferEventType, } - // updates sender and receiver addresses if tx is htlc, and if not leaves as it is. + /// Reads sender and receiver addresses properly from an IBC event. + fn read_real_ibc_addresses(transfer_details: &mut TransferDetails, msg_event: &Event) { + transfer_details.transfer_event_type = match msg_event.kind.as_str() { + IBC_SEND_EVENT => TransferEventType::IBCSend, + IBC_RECEIVE_EVENT | IBC_NFT_RECEIVE_EVENT => TransferEventType::IBCReceive, + _ => unreachable!("`read_real_ibc_addresses` shouldn't be called for non-IBC events."), + }; + + transfer_details.from = some_or_return!(get_value_from_event_attributes( + &msg_event.attributes, + SENDER_TAG_KEY, + SENDER_TAG_KEY_BASE64 + )); + + transfer_details.to = some_or_return!(get_value_from_event_attributes( + &msg_event.attributes, + RECEIVER_TAG_KEY, + RECEIVER_TAG_KEY_BASE64, + )); + } + + /// Reads sender and receiver addresses properly from an HTLC event. fn read_real_htlc_addresses(transfer_details: &mut TransferDetails, msg_event: &&Event) { match msg_event.kind.as_str() { CREATE_HTLC_EVENT => { - transfer_details.from = - some_or_return!(msg_event.attributes.iter().find(|tag| tag.key == SENDER_TAG_KEY)) - .value - .to_string(); - - transfer_details.to = - some_or_return!(msg_event.attributes.iter().find(|tag| tag.key == RECEIVER_TAG_KEY)) - .value - .to_string(); + transfer_details.from = some_or_return!(get_value_from_event_attributes( + &msg_event.attributes, + SENDER_TAG_KEY, + SENDER_TAG_KEY_BASE64 + )); + + transfer_details.to = some_or_return!(get_value_from_event_attributes( + &msg_event.attributes, + RECEIVER_TAG_KEY, + RECEIVER_TAG_KEY_BASE64, + )); transfer_details.transfer_event_type = TransferEventType::CreateHtlc; }, CLAIM_HTLC_EVENT => { - transfer_details.from = - some_or_return!(msg_event.attributes.iter().find(|tag| tag.key == SENDER_TAG_KEY)) - .value - .to_string(); + transfer_details.from = some_or_return!(get_value_from_event_attributes( + &msg_event.attributes, + SENDER_TAG_KEY, + SENDER_TAG_KEY_BASE64 + )); transfer_details.transfer_event_type = TransferEventType::ClaimHtlc; }, - _ => {}, + _ => unreachable!("`read_real_htlc_addresses` shouldn't be called for non-HTLC events."), } } fn parse_transfer_values_from_events(tx_events: Vec<&Event>) -> Vec { let mut transfer_details_list: Vec = vec![]; - for (index, event) in tx_events.iter().enumerate() { + for event in tx_events.iter() { if event.kind.as_str() == TRANSFER_EVENT { - let amount_with_denoms = - some_or_continue!(event.attributes.iter().find(|tag| tag.key == AMOUNT_TAG_KEY)) - .value - .to_string(); + let amount_with_denoms = some_or_continue!(get_value_from_event_attributes( + &event.attributes, + AMOUNT_TAG_KEY, + AMOUNT_TAG_KEY_BASE64 + )); + let amount_with_denoms = amount_with_denoms.split(','); for amount_with_denom in amount_with_denoms { @@ -434,13 +485,17 @@ where let denom = &amount_with_denom[extracted_amount.len()..]; let amount = some_or_continue!(extracted_amount.parse().ok()); - let from = some_or_continue!(event.attributes.iter().find(|tag| tag.key == SENDER_TAG_KEY)) - .value - .to_string(); + let from = some_or_continue!(get_value_from_event_attributes( + &event.attributes, + SENDER_TAG_KEY, + SENDER_TAG_KEY_BASE64 + )); - let to = some_or_continue!(event.attributes.iter().find(|tag| tag.key == RECIPIENT_TAG_KEY)) - .value - .to_string(); + let to = some_or_continue!(get_value_from_event_attributes( + &event.attributes, + RECIPIENT_TAG_KEY, + RECIPIENT_TAG_KEY_BASE64, + )); let mut tx_details = TransferDetails { from, @@ -451,14 +506,20 @@ where transfer_event_type: TransferEventType::default(), }; - if index != 0 { - // If previous message is htlc related, that means current transfer - // addresses will be wrong. - if let Some(prev_event) = tx_events.get(index - 1) { - if [CREATE_HTLC_EVENT, CLAIM_HTLC_EVENT].contains(&prev_event.kind.as_str()) { - read_real_htlc_addresses(&mut tx_details, prev_event); - } - }; + // For HTLC transactions, the sender and receiver addresses in the "transfer" event will be incorrect. + // Use `read_real_htlc_addresses` to handle them properly. + if let Some(htlc_event) = tx_events + .iter() + .find(|e| [CREATE_HTLC_EVENT, CLAIM_HTLC_EVENT].contains(&e.kind.as_str())) + { + read_real_htlc_addresses(&mut tx_details, htlc_event); + } + // For IBC transactions, the sender and receiver addresses in the "transfer" event will be incorrect. + // Use `read_real_ibc_addresses` to handle them properly. + else if let Some(ibc_event) = tx_events.iter().find(|e| { + [IBC_SEND_EVENT, IBC_RECEIVE_EVENT, IBC_NFT_RECEIVE_EVENT].contains(&e.kind.as_str()) + }) { + read_real_ibc_addresses(&mut tx_details, ibc_event); } // sum the amounts coins and pairs are same @@ -493,12 +554,8 @@ where // Retain fee related events events.retain(|event| { if event.kind == TRANSFER_EVENT { - let amount_with_denom = event - .attributes - .iter() - .find(|tag| tag.key == AMOUNT_TAG_KEY) - .map(|t| t.value.to_string()); - + let amount_with_denom = + get_value_from_event_attributes(&event.attributes, AMOUNT_TAG_KEY, AMOUNT_TAG_KEY_BASE64); amount_with_denom != Some(fee_amount_with_denom.clone()) } else { true @@ -547,7 +604,7 @@ where } }, TransferEventType::ClaimHtlc => Some((vec![my_address], vec![])), - TransferEventType::Standard => { + TransferEventType::Standard | TransferEventType::IBCSend | TransferEventType::IBCReceive => { Some((vec![transfer_details.from.clone()], vec![transfer_details.to.clone()])) }, } @@ -879,6 +936,24 @@ where } } +/// Find, decode (if needed) and return the event attribute value. +/// +/// If the attribute doesn't exist, or decoding fails, `None` will be returned. +fn get_value_from_event_attributes(events: &[EventAttribute], tag: &str, base64_encoded_tag: &str) -> Option { + let event_attribute = events + .iter() + .find(|attribute| attribute.key == tag || attribute.key == base64_encoded_tag)?; + + if event_attribute.key == base64_encoded_tag { + let decoded_bytes = base64::engine::general_purpose::STANDARD + .decode(event_attribute.value.clone()) + .ok()?; + String::from_utf8(decoded_bytes).ok() + } else { + Some(event_attribute.value.clone()) + } +} + pub async fn tendermint_history_loop( coin: TendermintCoin, storage: impl TxHistoryStorage, @@ -906,3 +981,101 @@ pub async fn tendermint_history_loop( .await .expect("The error of this machine is Infallible"); } + +#[cfg(any(test, target_arch = "wasm32"))] +mod tests { + use super::*; + use common::cross_test; + + common::cfg_wasm32! { + use wasm_bindgen_test::*; + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + } + + cross_test!(test_get_value_from_event_attributes, { + let attributes = vec![ + EventAttribute { + key: "recipient".to_owned(), + value: "nuc1erfnkjsmalkwtvj44qnfr2drfzdt4n9ledw63y".to_owned(), + index: false, + }, + EventAttribute { + key: "sender".to_owned(), + value: "nuc1a7xynj4ceft8kgdjr6kcq0s07y3ccya60rqwwn".to_owned(), + index: false, + }, + EventAttribute { + key: "amount".to_owned(), + value: "8000ibc/F7F28FF3C09024A0225EDBBDB207E5872D2B4EF2FB874FE47B05EF9C9A7D211C".to_owned(), + index: false, + }, + ]; + + let value = get_value_from_event_attributes(&attributes, "invalid", ""); + assert_eq!(value, None); + let value = get_value_from_event_attributes(&attributes, RECIPIENT_TAG_KEY, RECIPIENT_TAG_KEY_BASE64).unwrap(); + assert_eq!(value, "nuc1erfnkjsmalkwtvj44qnfr2drfzdt4n9ledw63y"); + let value = get_value_from_event_attributes(&attributes, SENDER_TAG_KEY, SENDER_TAG_KEY_BASE64).unwrap(); + assert_eq!(value, "nuc1a7xynj4ceft8kgdjr6kcq0s07y3ccya60rqwwn"); + let value = get_value_from_event_attributes(&attributes, AMOUNT_TAG_KEY, AMOUNT_TAG_KEY_BASE64).unwrap(); + assert_eq!( + value, + "8000ibc/F7F28FF3C09024A0225EDBBDB207E5872D2B4EF2FB874FE47B05EF9C9A7D211C" + ); + + let encoded_attributes = vec![ + EventAttribute { + key: "cmVjaXBpZW50".to_owned(), + value: "bnVjMTd4cGZ2YWttMmFtZzk2MnlsczZmODR6M2tlbGw4YzVsM3B6YTJ5".to_owned(), + index: true, + }, + EventAttribute { + key: "c2VuZGVy".to_owned(), + value: "bnVjMWE3eHluajRjZWZ0OGtnZGpyNmtjcTBzMDd5M2NjeWE2MHJxd3du".to_owned(), + index: true, + }, + EventAttribute { + key: "YW1vdW50".to_owned(), + value: "MjcxNjJ1bnVjbA==".to_owned(), + index: true, + }, + ]; + + let value = get_value_from_event_attributes(&encoded_attributes, "invalid", ""); + assert_eq!(value, None); + let value = + get_value_from_event_attributes(&encoded_attributes, RECIPIENT_TAG_KEY, RECIPIENT_TAG_KEY_BASE64).unwrap(); + assert_eq!(value, "nuc17xpfvakm2amg962yls6f84z3kell8c5l3pza2y"); + let value = + get_value_from_event_attributes(&encoded_attributes, SENDER_TAG_KEY, SENDER_TAG_KEY_BASE64).unwrap(); + assert_eq!(value, "nuc1a7xynj4ceft8kgdjr6kcq0s07y3ccya60rqwwn"); + let value = + get_value_from_event_attributes(&encoded_attributes, AMOUNT_TAG_KEY, AMOUNT_TAG_KEY_BASE64).unwrap(); + assert_eq!(value, "27162unucl"); + + let invalid_attributes = vec![ + EventAttribute { + key: String::default(), + value: String::default(), + index: true, + }, + EventAttribute { + key: "invalid-key".to_owned(), + value: String::default(), + index: true, + }, + EventAttribute { + key: "dummy-key".to_owned(), + value: String::default(), + index: true, + }, + ]; + + let value = get_value_from_event_attributes(&invalid_attributes, RECIPIENT_TAG_KEY, RECIPIENT_TAG_KEY_BASE64); + assert_eq!(value, None); + let value = get_value_from_event_attributes(&invalid_attributes, SENDER_TAG_KEY, SENDER_TAG_KEY_BASE64); + assert_eq!(value, None); + let value = get_value_from_event_attributes(&invalid_attributes, AMOUNT_TAG_KEY, AMOUNT_TAG_KEY_BASE64); + assert_eq!(value, None); + }); +} diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index c077bf60bd..43765ab0ba 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -1,8 +1,8 @@ #![allow(clippy::all)] -use super::{CoinBalance, FundingTxSpend, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, - RawTransactionRequest, SearchForFundingSpendErr, SwapOps, TradeFee, TransactionEnum, TransactionFut, - WaitForTakerPaymentSpendError}; +use super::{CoinBalance, CommonSwapOpsV2, FundingTxSpend, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, + RawTransactionRequest, RefundTakerPaymentArgs, SearchForFundingSpendErr, SwapOps, TradeFee, + TransactionEnum, TransactionFut, WaitForPaymentSpendError}; use crate::coin_errors::ValidatePaymentResult; use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenPreimageResult, GenTakerFundingSpendArgs, @@ -92,7 +92,7 @@ impl MarketCoinOps for TestCoin { unimplemented!() } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { unimplemented!() } + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { unimplemented!() } fn tx_enum_from_bytes(&self, _bytes: &[u8]) -> Result> { MmError::err(TxMarshalingErr::NotSupported( @@ -114,13 +114,17 @@ impl MarketCoinOps for TestCoin { #[async_trait] #[mockable] impl SwapOps for TestCoin { - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], _expire_at: u64) -> TransactionFut { + async fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionResult { unimplemented!() } - fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + unimplemented!() + } - fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + unimplemented!() + } async fn send_maker_spends_taker_payment( &self, @@ -150,7 +154,9 @@ impl SwapOps for TestCoin { unimplemented!() } - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { + unimplemented!() + } async fn validate_maker_payment(&self, _input: ValidatePaymentInput) -> ValidatePaymentResult<()> { unimplemented!() @@ -160,10 +166,10 @@ impl SwapOps for TestCoin { unimplemented!() } - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - _if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { unimplemented!() } @@ -209,9 +215,7 @@ impl SwapOps for TestCoin { fn derive_htlc_pubkey(&self, _swap_unique_data: &[u8]) -> Vec { unimplemented!() } - fn can_refund_htlc(&self, locktime: u64) -> Box + Send + '_> { - unimplemented!() - } + async fn can_refund_htlc(&self, locktime: u64) -> Result { unimplemented!() } fn validate_other_pubkey(&self, raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { unimplemented!() } @@ -472,7 +476,10 @@ impl TakerCoinSwapOpsV2 for TestCoin { unimplemented!() } - async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> Result { + async fn refund_taker_funding_timelock( + &self, + args: RefundTakerPaymentArgs<'_>, + ) -> Result { todo!() } @@ -517,7 +524,10 @@ impl TakerCoinSwapOpsV2 for TestCoin { todo!() } - async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> Result { + async fn refund_combined_taker_payment( + &self, + args: RefundTakerPaymentArgs<'_>, + ) -> Result { unimplemented!() } @@ -552,9 +562,13 @@ impl TakerCoinSwapOpsV2 for TestCoin { taker_payment: &Self::Tx, from_block: u64, wait_until: u64, - ) -> MmResult { + ) -> MmResult { unimplemented!() } +} + +impl CommonSwapOpsV2 for TestCoin { + fn derive_htlc_pubkey_v2(&self, _swap_unique_data: &[u8]) -> Self::Pubkey { todo!() } - fn derive_htlc_pubkey_v2(&self, swap_unique_data: &[u8]) -> Self::Pubkey { todo!() } + fn derive_htlc_pubkey_v2_bytes(&self, _swap_unique_data: &[u8]) -> Vec { todo!() } } diff --git a/mm2src/coins/tx_history_storage/sql_tx_history_storage_v2.rs b/mm2src/coins/tx_history_storage/sql_tx_history_storage_v2.rs index cf0575f973..f5b1312a65 100644 --- a/mm2src/coins/tx_history_storage/sql_tx_history_storage_v2.rs +++ b/mm2src/coins/tx_history_storage/sql_tx_history_storage_v2.rs @@ -376,11 +376,12 @@ pub struct SqliteTxHistoryStorage(Arc>); impl SqliteTxHistoryStorage { pub fn new(ctx: &MmArc) -> Result> { - let sqlite_connection = ctx - .sqlite_connection - .ok_or(MmError::new(CreateTxHistoryStorageError::Internal( - "sqlite_connection is not initialized".to_owned(), - )))?; + let sqlite_connection = + ctx.sqlite_connection + .get() + .ok_or(MmError::new(CreateTxHistoryStorageError::Internal( + "sqlite_connection is not initialized".to_owned(), + )))?; Ok(SqliteTxHistoryStorage(sqlite_connection.clone())) } } diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index ebd7f72f29..6d98451c7f 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -53,7 +53,7 @@ use common::first_char_to_upper; use common::jsonrpc_client::JsonRpcError; use common::log::LogOnError; use common::{now_sec, now_sec_u32}; -use crypto::{Bip32Error, DerivationPath, HDPathToCoin, Secp256k1ExtendedPublicKey, StandardHDPathError}; +use crypto::{DerivationPath, HDPathToCoin, Secp256k1ExtendedPublicKey}; use derive_more::Display; #[cfg(not(target_arch = "wasm32"))] use dirs::home_dir; use futures::channel::mpsc::{Receiver as AsyncReceiver, Sender as AsyncSender, UnboundedReceiver, UnboundedSender}; @@ -99,19 +99,17 @@ use utxo_hd_wallet::UtxoHDWallet; use utxo_signer::with_key_pair::sign_tx; use utxo_signer::{TxProvider, TxProviderError, UtxoSignTxError, UtxoSignTxResult}; -use self::rpc_clients::{electrum_script_hash, ElectrumClient, ElectrumRpcRequest, EstimateFeeMethod, EstimateFeeMode, - NativeClient, UnspentInfo, UnspentMap, UtxoRpcClientEnum, UtxoRpcError, UtxoRpcFut, - UtxoRpcResult}; +use self::rpc_clients::{electrum_script_hash, ElectrumClient, ElectrumConnectionSettings, EstimateFeeMethod, + EstimateFeeMode, NativeClient, UnspentInfo, UnspentMap, UtxoRpcClientEnum, UtxoRpcError, + UtxoRpcFut, UtxoRpcResult}; use super::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BalanceResult, CoinBalance, CoinFutSpawner, CoinsContext, DerivationMethod, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, KmdRewardsDetails, MarketCoinOps, MmCoin, NumConversError, NumConversResult, PrivKeyActivationPolicy, PrivKeyPolicy, - PrivKeyPolicyNotAllowed, RawTransactionFut, RpcTransportEventHandler, RpcTransportEventHandlerShared, - TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, Transaction, TransactionDetails, - TransactionEnum, TransactionErr, UnexpectedDerivationMethod, VerificationError, WithdrawError, - WithdrawRequest}; + PrivKeyPolicyNotAllowed, RawTransactionFut, TradeFee, TradePreimageError, TradePreimageFut, + TradePreimageResult, Transaction, TransactionDetails, TransactionEnum, TransactionErr, + UnexpectedDerivationMethod, VerificationError, WithdrawError, WithdrawRequest}; use crate::coin_balance::{EnableCoinScanPolicy, EnabledCoinBalanceParams, HDAddressBalanceScanner}; -use crate::hd_wallet::{HDAccountOps, HDAddressOps, HDPathAccountToAddressId, HDWalletCoinOps, HDWalletOps, - HDWalletStorageError}; +use crate::hd_wallet::{HDAccountOps, HDAddressOps, HDPathAccountToAddressId, HDWalletCoinOps, HDWalletOps}; use crate::utxo::tx_cache::UtxoVerboseCacheShared; use crate::{ParseCoinAssocTypes, ToBytes}; @@ -144,7 +142,6 @@ pub type RecentlySpentOutPointsGuard<'a> = AsyncMutexGuard<'a, RecentlySpentOutP pub enum ScripthashNotification { Triggered(String), SubscribeToAddresses(HashSet
), - RefreshSubscriptions, } pub type ScripthashNotificationSender = Option>; @@ -243,14 +240,6 @@ impl From for TxProviderError { } } -impl From for HDWalletStorageError { - fn from(e: StandardHDPathError) -> Self { HDWalletStorageError::ErrorDeserializing(e.to_string()) } -} - -impl From for HDWalletStorageError { - fn from(e: Bip32Error) -> Self { HDWalletStorageError::ErrorDeserializing(e.to_string()) } -} - #[async_trait] impl TxProvider for UtxoRpcClientEnum { async fn get_rpc_transaction(&self, tx_hash: &H256Json) -> Result> { @@ -1395,43 +1384,6 @@ pub fn coin_daemon_data_dir(name: &str, is_asset_chain: bool) -> PathBuf { data_dir } -enum ElectrumProtoVerifierEvent { - Connected(String), - Disconnected(String), -} - -/// Electrum protocol version verifier. -/// The structure is used to handle the `on_connected` event and notify `electrum_version_loop`. -struct ElectrumProtoVerifier { - on_event_tx: UnboundedSender, -} - -impl ElectrumProtoVerifier { - fn into_shared(self) -> RpcTransportEventHandlerShared { Arc::new(self) } -} - -impl RpcTransportEventHandler for ElectrumProtoVerifier { - fn debug_info(&self) -> String { "ElectrumProtoVerifier".into() } - - fn on_outgoing_request(&self, _data: &[u8]) {} - - fn on_incoming_response(&self, _data: &[u8]) {} - - fn on_connected(&self, address: String) -> Result<(), String> { - try_s!(self - .on_event_tx - .unbounded_send(ElectrumProtoVerifierEvent::Connected(address))); - Ok(()) - } - - fn on_disconnected(&self, address: String) -> Result<(), String> { - try_s!(self - .on_event_tx - .unbounded_send(ElectrumProtoVerifierEvent::Disconnected(address))); - Ok(()) - } -} - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct UtxoActivationParams { pub mode: UtxoRpcMode, @@ -1481,7 +1433,13 @@ impl UtxoActivationParams { Some("electrum") => { let servers = json::from_value(req["servers"].clone()).map_to_mm(UtxoFromLegacyReqErr::InvalidElectrumServers)?; - UtxoRpcMode::Electrum { servers } + let min_connected = req["min_connected"].as_u64().map(|m| m as usize); + let max_connected = req["max_connected"].as_u64().map(|m| m as usize); + UtxoRpcMode::Electrum { + servers, + min_connected, + max_connected, + } }, _ => return MmError::err(UtxoFromLegacyReqErr::UnexpectedMethod), }; @@ -1533,7 +1491,14 @@ impl UtxoActivationParams { #[serde(tag = "rpc", content = "rpc_data")] pub enum UtxoRpcMode { Native, - Electrum { servers: Vec }, + Electrum { + /// The settings of each electrum server. + servers: Vec, + /// The minimum number of connections to electrum servers to keep alive/maintained at all times. + min_connected: Option, + /// The maximum number of connections to electrum servers to not exceed at any time. + max_connected: Option, + }, } impl UtxoRpcMode { diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index 2b94135933..d71b6538e3 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -1,9 +1,8 @@ use super::*; -use crate::coin_balance::{EnableCoinBalanceError, HDAddressBalance, HDBalanceAddress, HDWalletBalance, - HDWalletBalanceOps}; +use crate::coin_balance::{EnableCoinBalanceError, HDAddressBalance, HDWalletBalance, HDWalletBalanceOps}; use crate::coin_errors::{MyAddressError, ValidatePaymentResult}; -use crate::hd_wallet::{ExtractExtendedPubkey, HDCoinAddress, HDCoinHDAccount, HDCoinHDAddress, HDCoinWithdrawOps, - HDExtractPubkeyError, HDXPubExtractor, TrezorCoinError, WithdrawSenderAddress}; +use crate::hd_wallet::{ExtractExtendedPubkey, HDCoinAddress, HDCoinWithdrawOps, HDExtractPubkeyError, HDXPubExtractor, + TrezorCoinError, WithdrawSenderAddress}; use crate::my_tx_history_v2::{CoinWithTxHistoryV2, MyTxHistoryErrorV2, MyTxHistoryTarget, TxDetailsBuilder, TxHistoryStorage}; use crate::tx_history_storage::{GetTxHistoryFilters, WalletId}; @@ -11,20 +10,21 @@ use crate::utxo::rpc_clients::UtxoRpcFut; use crate::utxo::slp::{parse_slp_script, SlpGenesisParams, SlpTokenInfo, SlpTransaction, SlpUnspent}; use crate::utxo::utxo_builder::{UtxoArcBuilder, UtxoCoinBuilder}; use crate::utxo::utxo_common::{big_decimal_from_sat_unsigned, utxo_prepare_addresses_for_balance_stream_if_enabled}; +use crate::utxo::utxo_hd_wallet::{UtxoHDAccount, UtxoHDAddress}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; -use crate::{coin_balance, BlockHeightAndTime, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinProtocol, - CoinWithDerivationMethod, CoinWithPrivKeyPolicy, ConfirmPaymentInput, DexFee, GetWithdrawSenderAddress, - IguanaBalanceOps, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, - PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, - RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, - RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, - TradePreimageValue, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, - WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, +use crate::{coin_balance, BlockHeightAndTime, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinBalanceMap, + CoinProtocol, CoinWithDerivationMethod, CoinWithPrivKeyPolicy, ConfirmPaymentInput, DexFee, + GetWithdrawSenderAddress, IguanaBalanceOps, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, + NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, + PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, + RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, + TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; use common::executor::{AbortableSystem, AbortedError}; use common::log::warn; @@ -870,18 +870,30 @@ impl UtxoCommonOps for BchCoin { #[async_trait] impl SwapOps for BchCoin { #[inline] - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { + async fn send_taker_fee( + &self, + fee_addr: &[u8], + dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { utxo_common::send_taker_fee(self.clone(), fee_addr, dex_fee) + .compat() + .await } #[inline] - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { utxo_common::send_maker_payment(self.clone(), maker_payment_args) + .compat() + .await } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { utxo_common::send_taker_payment(self.clone(), taker_payment_args) + .compat() + .await } #[inline] @@ -910,10 +922,15 @@ impl SwapOps for BchCoin { utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args).await } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), - _ => panic!(), + fee_tx => { + return MmError::err(ValidatePaymentError::InternalError(format!( + "Invalid fee tx type. fee tx: {:?}", + fee_tx + ))) + }, }; utxo_common::validate_fee( self.clone(), @@ -924,6 +941,8 @@ impl SwapOps for BchCoin { validate_fee_args.min_block_number, validate_fee_args.fee_addr, ) + .compat() + .await } #[inline] @@ -937,17 +956,23 @@ impl SwapOps for BchCoin { } #[inline] - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { + let time_lock = if_my_payment_sent_args + .time_lock + .try_into() + .map_err(|e: TryFromIntError| e.to_string())?; utxo_common::check_if_my_payment_sent( self.clone(), - try_fus!(if_my_payment_sent_args.time_lock.try_into()), + time_lock, if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, ) + .compat() + .await } #[inline] @@ -990,13 +1015,10 @@ impl SwapOps for BchCoin { } #[inline] - fn can_refund_htlc(&self, locktime: u64) -> Box + Send + '_> { - Box::new( - utxo_common::can_refund_htlc(self, locktime) - .boxed() - .map_err(|e| ERRL!("{}", e)) - .compat(), - ) + async fn can_refund_htlc(&self, locktime: u64) -> Result { + utxo_common::can_refund_htlc(self, locktime) + .await + .map_err(|e| ERRL!("{}", e)) } #[inline] @@ -1229,7 +1251,7 @@ impl MarketCoinOps for BchCoin { utxo_common::wait_for_confirmations(&self.utxo_arc, input) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { utxo_common::wait_for_output_spend( self.clone(), args.tx_bytes, @@ -1238,6 +1260,7 @@ impl MarketCoinOps for BchCoin { args.wait_until, args.check_every, ) + .await } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -1416,7 +1439,7 @@ impl HDWalletCoinOps for BchCoin { &self, extended_pubkey: &Secp256k1ExtendedPublicKey, derivation_path: DerivationPath, - ) -> HDCoinHDAddress { + ) -> UtxoHDAddress { utxo_common::address_from_extended_pubkey(self, extended_pubkey, derivation_path) } @@ -1428,7 +1451,7 @@ impl HDCoinWithdrawOps for BchCoin {} #[async_trait] impl HDWalletBalanceOps for BchCoin { type HDAddressScanner = UtxoAddressScanner; - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn produce_hd_address_scanner(&self) -> BalanceResult { utxo_common::produce_hd_address_scanner(self).await @@ -1450,7 +1473,7 @@ impl HDWalletBalanceOps for BchCoin { async fn scan_for_new_addresses( &self, hd_wallet: &Self::HDWallet, - hd_account: &mut HDCoinHDAccount, + hd_account: &mut UtxoHDAccount, address_scanner: &Self::HDAddressScanner, gap_limit: u32, ) -> BalanceResult>> { @@ -1459,20 +1482,27 @@ impl HDWalletBalanceOps for BchCoin { async fn all_known_addresses_balances( &self, - hd_account: &HDCoinHDAccount, + hd_account: &UtxoHDAccount, ) -> BalanceResult>> { utxo_common::all_known_addresses_balances(self, hd_account).await } - async fn known_address_balance(&self, address: &HDBalanceAddress) -> BalanceResult { - utxo_common::address_balance(self, address).await + async fn known_address_balance(&self, address: &Address) -> BalanceResult { + let balance = utxo_common::address_balance(self, address).await?; + Ok(HashMap::from([(self.ticker().to_string(), balance)])) } async fn known_addresses_balances( &self, - addresses: Vec>, - ) -> BalanceResult, Self::BalanceObject)>> { - utxo_common::addresses_balances(self, addresses).await + addresses: Vec
, + ) -> BalanceResult> { + let ticker = self.ticker().to_string(); + let balances = utxo_common::addresses_balances(self, addresses).await?; + + balances + .into_iter() + .map(|(address, balance)| Ok((address, HashMap::from([(ticker.clone(), balance)])))) + .collect() } async fn prepare_addresses_for_balance_stream_if_enabled( diff --git a/mm2src/coins/utxo/pb.rs b/mm2src/coins/utxo/pb.rs index 3ae572bc23..a9fb741a11 100644 --- a/mm2src/coins/utxo/pb.rs +++ b/mm2src/coins/utxo/pb.rs @@ -1,3 +1,4 @@ +// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetMempoolInfoRequest {} diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index 8b8a60d246..c5fbc67293 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -1,10 +1,10 @@ use super::utxo_common::utxo_prepare_addresses_for_balance_stream_if_enabled; use super::*; use crate::coin_balance::{self, EnableCoinBalanceError, EnabledCoinBalanceParams, HDAccountBalance, HDAddressBalance, - HDBalanceAddress, HDWalletBalance, HDWalletBalanceOps}; + HDWalletBalance, HDWalletBalanceOps}; use crate::coin_errors::{MyAddressError, ValidatePaymentResult}; -use crate::hd_wallet::{ExtractExtendedPubkey, HDCoinAddress, HDCoinHDAccount, HDCoinHDAddress, HDCoinWithdrawOps, - HDConfirmAddress, HDExtractPubkeyError, HDXPubExtractor, TrezorCoinError, WithdrawSenderAddress}; +use crate::hd_wallet::{ExtractExtendedPubkey, HDCoinAddress, HDCoinWithdrawOps, HDConfirmAddress, + HDExtractPubkeyError, HDXPubExtractor, TrezorCoinError, WithdrawSenderAddress}; use crate::my_tx_history_v2::{CoinWithTxHistoryV2, MyTxHistoryErrorV2, MyTxHistoryTarget, TxHistoryStorage}; use crate::rpc_command::account_balance::{self, AccountBalanceParams, AccountBalanceRpcOps, HDAccountBalanceResponse}; use crate::rpc_command::get_new_address::{self, GetNewAddressParams, GetNewAddressResponse, GetNewAddressRpcError, @@ -20,9 +20,10 @@ use crate::tx_history_storage::{GetTxHistoryFilters, WalletId}; use crate::utxo::utxo_builder::{MergeUtxoArcOps, UtxoCoinBuildError, UtxoCoinBuilder, UtxoCoinBuilderCommonOps, UtxoFieldsWithGlobalHDBuilder, UtxoFieldsWithHardwareWalletBuilder, UtxoFieldsWithIguanaSecretBuilder}; +use crate::utxo::utxo_hd_wallet::{UtxoHDAccount, UtxoHDAddress}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; -use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, +use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinBalanceMap, CoinWithDerivationMethod, CoinWithPrivKeyPolicy, ConfirmPaymentInput, DelegationError, DelegationFut, DexFee, GetWithdrawSenderAddress, IguanaBalanceOps, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, @@ -509,18 +510,30 @@ impl UtxoStandardOps for QtumCoin { #[async_trait] impl SwapOps for QtumCoin { #[inline] - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { + async fn send_taker_fee( + &self, + fee_addr: &[u8], + dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { utxo_common::send_taker_fee(self.clone(), fee_addr, dex_fee) + .compat() + .await } #[inline] - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { utxo_common::send_maker_payment(self.clone(), maker_payment_args) + .compat() + .await } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { utxo_common::send_taker_payment(self.clone(), taker_payment_args) + .compat() + .await } #[inline] @@ -549,10 +562,15 @@ impl SwapOps for QtumCoin { utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args).await } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), - _ => panic!(), + fee_tx => { + return MmError::err(ValidatePaymentError::InternalError(format!( + "Invalid fee tx type. fee tx: {:?}", + fee_tx + ))) + }, }; utxo_common::validate_fee( self.clone(), @@ -563,6 +581,8 @@ impl SwapOps for QtumCoin { validate_fee_args.min_block_number, validate_fee_args.fee_addr, ) + .compat() + .await } #[inline] @@ -576,17 +596,23 @@ impl SwapOps for QtumCoin { } #[inline] - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { + let time_lock = if_my_payment_sent_args + .time_lock + .try_into() + .map_err(|e: TryFromIntError| e.to_string())?; utxo_common::check_if_my_payment_sent( self.clone(), - try_fus!(if_my_payment_sent_args.time_lock.try_into()), + time_lock, if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, ) + .compat() + .await } #[inline] @@ -629,13 +655,10 @@ impl SwapOps for QtumCoin { } #[inline] - fn can_refund_htlc(&self, locktime: u64) -> Box + Send + '_> { - Box::new( - utxo_common::can_refund_htlc(self, locktime) - .boxed() - .map_err(|e| ERRL!("{}", e)) - .compat(), - ) + async fn can_refund_htlc(&self, locktime: u64) -> Result { + utxo_common::can_refund_htlc(self, locktime) + .await + .map_err(|e| ERRL!("{}", e)) } #[inline] @@ -848,7 +871,7 @@ impl MarketCoinOps for QtumCoin { utxo_common::wait_for_confirmations(&self.utxo_arc, input) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { utxo_common::wait_for_output_spend( self.clone(), args.tx_bytes, @@ -857,6 +880,7 @@ impl MarketCoinOps for QtumCoin { args.wait_until, args.check_every, ) + .await } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -1039,9 +1063,12 @@ impl CoinWithDerivationMethod for QtumCoin { #[async_trait] impl IguanaBalanceOps for QtumCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; - async fn iguana_balances(&self) -> BalanceResult { self.my_balance().compat().await } + async fn iguana_balances(&self) -> BalanceResult { + let balance = self.my_balance().compat().await?; + Ok(HashMap::from([(self.ticker().to_string(), balance)])) + } } #[async_trait] @@ -1068,7 +1095,7 @@ impl HDWalletCoinOps for QtumCoin { &self, extended_pubkey: &Secp256k1ExtendedPublicKey, derivation_path: DerivationPath, - ) -> HDCoinHDAddress { + ) -> UtxoHDAddress { utxo_common::address_from_extended_pubkey(self, extended_pubkey, derivation_path) } @@ -1080,7 +1107,7 @@ impl HDCoinWithdrawOps for QtumCoin {} #[async_trait] impl HDWalletBalanceOps for QtumCoin { type HDAddressScanner = UtxoAddressScanner; - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn produce_hd_address_scanner(&self) -> BalanceResult { utxo_common::produce_hd_address_scanner(self).await @@ -1102,7 +1129,7 @@ impl HDWalletBalanceOps for QtumCoin { async fn scan_for_new_addresses( &self, hd_wallet: &Self::HDWallet, - hd_account: &mut HDCoinHDAccount, + hd_account: &mut UtxoHDAccount, address_scanner: &Self::HDAddressScanner, gap_limit: u32, ) -> BalanceResult>> { @@ -1111,20 +1138,27 @@ impl HDWalletBalanceOps for QtumCoin { async fn all_known_addresses_balances( &self, - hd_account: &HDCoinHDAccount, + hd_account: &UtxoHDAccount, ) -> BalanceResult>> { utxo_common::all_known_addresses_balances(self, hd_account).await } - async fn known_address_balance(&self, address: &HDBalanceAddress) -> BalanceResult { - utxo_common::address_balance(self, address).await + async fn known_address_balance(&self, address: &Address) -> BalanceResult { + let balance = utxo_common::address_balance(self, address).await?; + Ok(HashMap::from([(self.ticker().to_string(), balance)])) } async fn known_addresses_balances( &self, - addresses: Vec>, - ) -> BalanceResult, Self::BalanceObject)>> { - utxo_common::addresses_balances(self, addresses).await + addresses: Vec
, + ) -> BalanceResult> { + let ticker = self.ticker().to_string(); + let balances = utxo_common::addresses_balances(self, addresses).await?; + + balances + .into_iter() + .map(|(address, balance)| Ok((address, HashMap::from([(ticker.clone(), balance)])))) + .collect() } async fn prepare_addresses_for_balance_stream_if_enabled( @@ -1137,7 +1171,7 @@ impl HDWalletBalanceOps for QtumCoin { #[async_trait] impl GetNewAddressRpcOps for QtumCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn get_new_address_rpc_without_conf( &self, @@ -1160,7 +1194,7 @@ impl GetNewAddressRpcOps for QtumCoin { #[async_trait] impl AccountBalanceRpcOps for QtumCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn account_balance_rpc( &self, @@ -1172,7 +1206,7 @@ impl AccountBalanceRpcOps for QtumCoin { #[async_trait] impl InitAccountBalanceRpcOps for QtumCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn init_account_balance_rpc( &self, @@ -1184,7 +1218,7 @@ impl InitAccountBalanceRpcOps for QtumCoin { #[async_trait] impl InitScanAddressesRpcOps for QtumCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn init_scan_for_new_addresses_rpc( &self, @@ -1196,7 +1230,7 @@ impl InitScanAddressesRpcOps for QtumCoin { #[async_trait] impl InitCreateAccountRpcOps for QtumCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn init_create_account_rpc( &self, diff --git a/mm2src/coins/utxo/qtum_delegation.rs b/mm2src/coins/utxo/qtum_delegation.rs index ad9aec86e1..f7573fafbf 100644 --- a/mm2src/coins/utxo/qtum_delegation.rs +++ b/mm2src/coins/utxo/qtum_delegation.rs @@ -45,7 +45,7 @@ pub enum QtumStakingAbiError { #[display(fmt = "Invalid QRC20 ABI params: {}", _0)] InvalidParams(String), #[display(fmt = "QRC20 ABI error: {}", _0)] - AbiError(String), + ABIError(String), #[display(fmt = "Qtum POD error: {}", _0)] PodSigningError(String), #[display(fmt = "Internal error: {}", _0)] @@ -56,7 +56,7 @@ impl From for QtumStakingAbiError { fn from(e: Qrc20AbiError) -> Self { match e { Qrc20AbiError::InvalidParams(e) => QtumStakingAbiError::InvalidParams(e), - Qrc20AbiError::AbiError(e) => QtumStakingAbiError::AbiError(e), + Qrc20AbiError::ABIError(e) => QtumStakingAbiError::ABIError(e), } } } @@ -66,7 +66,7 @@ impl From for DelegationError { } impl From for QtumStakingAbiError { - fn from(e: ethabi::Error) -> QtumStakingAbiError { QtumStakingAbiError::AbiError(e.to_string()) } + fn from(e: ethabi::Error) -> QtumStakingAbiError { QtumStakingAbiError::ABIError(e.to_string()) } } impl From for DelegationError { diff --git a/mm2src/coins/utxo/rpc_clients.rs b/mm2src/coins/utxo/rpc_clients.rs index e8f86bc8e0..fce6afa82a 100644 --- a/mm2src/coins/utxo/rpc_clients.rs +++ b/mm2src/coins/utxo/rpc_clients.rs @@ -1,78 +1,51 @@ #![cfg_attr(target_arch = "wasm32", allow(unused_macros))] #![cfg_attr(target_arch = "wasm32", allow(dead_code))] -use crate::utxo::utxo_block_header_storage::BlockHeaderStorage; -use crate::utxo::{output_script, output_script_p2pk, sat_from_big_decimal, GetBlockHeaderError, GetConfirmedTxError, - GetTxError, GetTxHeightError, NumConversResult, ScripthashNotification}; -use crate::{big_decimal_from_sat_unsigned, MyAddressError, NumConversError, RpcTransportEventHandler, - RpcTransportEventHandlerShared}; -use async_trait::async_trait; -use chain::{BlockHeader, BlockHeaderBits, BlockHeaderNonce, OutPoint, Transaction as UtxoTx, TransactionInput, - TxHashAlgo}; -use common::custom_futures::{select_ok_sequential, timeout::FutureTimerExt}; +mod electrum_rpc; +pub use electrum_rpc::*; + +use crate::utxo::{sat_from_big_decimal, GetBlockHeaderError, GetTxError, NumConversError, NumConversResult}; +use crate::{big_decimal_from_sat_unsigned, MyAddressError, RpcTransportEventHandlerShared}; +use chain::{OutPoint, Transaction as UtxoTx, TransactionInput, TxHashAlgo}; use common::custom_iter::TryIntoGroupMap; -use common::executor::{abortable_queue, abortable_queue::AbortableQueue, AbortableSystem, SpawnFuture, Timer}; -use common::jsonrpc_client::{JsonRpcBatchClient, JsonRpcBatchResponse, JsonRpcClient, JsonRpcError, JsonRpcErrorType, - JsonRpcId, JsonRpcMultiClient, JsonRpcRemoteAddr, JsonRpcRequest, JsonRpcRequestEnum, - JsonRpcResponse, JsonRpcResponseEnum, JsonRpcResponseFut, RpcRes}; -use common::log::{debug, LogOnError}; +use common::executor::Timer; +use common::jsonrpc_client::{JsonRpcBatchClient, JsonRpcClient, JsonRpcError, JsonRpcErrorType, JsonRpcRequest, + JsonRpcRequestEnum, JsonRpcResponseFut, RpcRes}; use common::log::{error, info, warn}; -use common::{median, now_float, now_ms, now_sec, OrdRange}; -use derive_more::Display; +use common::{median, now_sec}; use enum_derives::EnumFromStringify; -use futures::channel::oneshot as async_oneshot; -use futures::compat::{Future01CompatExt, Stream01CompatExt}; -use futures::future::{join_all, FutureExt, TryFutureExt}; -use futures::lock::Mutex as AsyncMutex; -use futures::{select, StreamExt}; -use futures01::future::select_ok; -use futures01::sync::mpsc; -use futures01::{Future, Sink, Stream}; -use http::Uri; -use itertools::Itertools; use keys::hash::H256; use keys::Address; use mm2_err_handle::prelude::*; -use mm2_number::{BigDecimal, BigInt, MmNumber}; -use mm2_rpc::data::legacy::ElectrumProtocol; -#[cfg(test)] use mocktopus::macros::*; +use mm2_number::{BigDecimal, MmNumber}; use rpc::v1::types::{Bytes as BytesJson, Transaction as RpcTransaction, H256 as H256Json}; use script::Script; -use serde_json::{self as json, Value as Json}; -use serialization::{deserialize, serialize, serialize_with_flags, CoinVariant, CompactInteger, Reader, - SERIALIZE_TRANSACTION_WITNESS}; -use sha2::{Digest, Sha256}; -use spv_validation::helpers_validation::SPVError; -use spv_validation::storage::BlockHeaderStorageOps; -use std::collections::hash_map::Entry; +use serialization::{deserialize, serialize, serialize_with_flags, CoinVariant, SERIALIZE_TRANSACTION_WITNESS}; + use std::collections::HashMap; -use std::convert::TryInto; use std::fmt; -use std::io; -use std::net::{SocketAddr, ToSocketAddrs}; +use std::fmt::Debug; use std::num::NonZeroU64; use std::ops::Deref; use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; use std::sync::Arc; -use std::time::Duration; -use super::ScripthashNotificationSender; +use async_trait::async_trait; +use derive_more::Display; +use futures::channel::oneshot as async_oneshot; +use futures::compat::Future01CompatExt; +use futures::future::{FutureExt, TryFutureExt}; +use futures::lock::Mutex as AsyncMutex; +use futures01::Future; +#[cfg(test)] use mocktopus::macros::*; +use serde_json::{self as json, Value as Json}; cfg_native! { - use futures::future::Either; - use futures::io::Error; + use crate::RpcTransportEventHandler; + use common::jsonrpc_client::{JsonRpcRemoteAddr, JsonRpcResponseEnum}; + use http::header::AUTHORIZATION; use http::{Request, StatusCode}; - use rustls::client::ServerCertVerified; - use rustls::{Certificate, ClientConfig, ServerName, OwnedTrustAnchor, RootCertStore}; - use std::convert::TryFrom; - use std::pin::Pin; - use std::task::{Context, Poll}; - use std::time::SystemTime; - use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, ReadBuf}; - use tokio::net::TcpStream; - use tokio_rustls::{client::TlsStream, TlsConnector}; - use webpki_roots::TLS_SERVER_ROOTS; } pub const NO_TX_ERROR_CODE: &str = "'code': -5"; @@ -80,39 +53,15 @@ const RESPONSE_TOO_LARGE_CODE: i16 = -32600; const TX_NOT_FOUND_RETRIES: u8 = 10; pub type AddressesByLabelResult = HashMap; -pub type JsonRpcPendingRequestsShared = Arc>; -pub type JsonRpcPendingRequests = HashMap>; pub type UnspentMap = HashMap>; -type ElectrumTxHistory = Vec; -type ElectrumScriptHash = String; -type ScriptHashUnspents = Vec; - #[derive(Debug, Deserialize)] #[allow(dead_code)] pub struct AddressPurpose { purpose: String, } -/// Skips the server certificate verification on TLS connection -pub struct NoCertificateVerification {} - -#[cfg(not(target_arch = "wasm32"))] -impl rustls::client::ServerCertVerifier for NoCertificateVerification { - fn verify_server_cert( - &self, - _: &Certificate, - _: &[Certificate], - _: &ServerName, - _: &mut dyn Iterator, - _: &[u8], - _: SystemTime, - ) -> Result { - Ok(rustls::client::ServerCertVerified::assertion()) - } -} - -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum UtxoRpcClientEnum { Native(NativeClient), Electrum(ElectrumClient), @@ -145,15 +94,6 @@ impl Deref for UtxoRpcClientEnum { } } -impl Clone for UtxoRpcClientEnum { - fn clone(&self) -> Self { - match self { - UtxoRpcClientEnum::Native(c) => UtxoRpcClientEnum::Native(c.clone()), - UtxoRpcClientEnum::Electrum(c) => UtxoRpcClientEnum::Electrum(c.clone()), - } - } -} - impl UtxoRpcClientEnum { pub fn wait_for_confirmations( &self, @@ -306,6 +246,14 @@ pub struct SpentOutputInfo { pub spent_in_block: BlockHashOrHeight, } +#[allow(clippy::upper_case_acronyms)] +#[derive(Debug, Deserialize, Serialize)] +pub enum EstimateFeeMode { + ECONOMICAL, + CONSERVATIVE, + UNSET, +} + pub type UtxoRpcResult = Result>; pub type UtxoRpcFut = Box> + Send + 'static>; @@ -381,7 +329,8 @@ pub trait UtxoRpcClientOps: fmt::Debug + Send + Sync + 'static { /// Submits the raw `tx` transaction (serialized, hex-encoded) to blockchain network. fn send_raw_transaction(&self, tx: BytesJson) -> UtxoRpcFut; - fn blockchain_scripthash_subscribe(&self, scripthash: String) -> UtxoRpcFut; + /// Subscribe to scripthash notifications from `server_address` for the given `scripthash`. + fn blockchain_scripthash_subscribe_using(&self, server_address: &str, scripthash: String) -> UtxoRpcFut; /// Returns raw transaction (serialized, hex-encoded) by the given `txid`. fn get_transaction_bytes(&self, txid: &H256Json) -> UtxoRpcFut; @@ -656,6 +605,66 @@ pub struct ListUnspentArgs { addresses: Vec, } +#[derive(Debug)] +struct ConcurrentRequestState { + is_running: bool, + subscribers: Vec>, +} + +impl ConcurrentRequestState { + fn new() -> Self { + ConcurrentRequestState { + is_running: false, + subscribers: Vec::new(), + } + } +} + +#[derive(Debug)] +pub struct ConcurrentRequestMap { + inner: AsyncMutex>>, +} + +impl Default for ConcurrentRequestMap { + fn default() -> Self { + ConcurrentRequestMap { + inner: AsyncMutex::new(HashMap::new()), + } + } +} + +impl ConcurrentRequestMap { + pub fn new() -> ConcurrentRequestMap { ConcurrentRequestMap::default() } + + async fn wrap_request(&self, request_arg: K, request_fut: RpcRes) -> Result { + let mut map = self.inner.lock().await; + let state = map + .entry(request_arg.clone()) + .or_insert_with(ConcurrentRequestState::new); + if state.is_running { + let (tx, rx) = async_oneshot::channel(); + state.subscribers.push(tx); + // drop here to avoid holding the lock during await + drop(map); + rx.await.unwrap() + } else { + state.is_running = true; + // drop here to avoid holding the lock during await + drop(map); + let request_res = request_fut.compat().await; + let mut map = self.inner.lock().await; + let state = map.get_mut(&request_arg).unwrap(); + for sub in state.subscribers.drain(..) { + if sub.send(request_res.clone()).is_err() { + warn!("subscriber is dropped"); + } + } + state.is_running = false; + request_res + } + } +} + /// RPC client for UTXO based coins /// https://developer.bitcoin.org/reference/rpc/index.html - Bitcoin RPC API reference /// Other coins have additional methods or miss some of these @@ -711,7 +720,7 @@ impl UtxoJsonRpcClientInfo for NativeClientImpl { impl JsonRpcClient for NativeClientImpl { fn version(&self) -> &'static str { "1.0" } - fn next_id(&self) -> String { self.request_id.fetch_add(1, AtomicOrdering::Relaxed).to_string() } + fn next_id(&self) -> u64 { self.request_id.fetch_add(1, AtomicOrdering::Relaxed) } fn client_info(&self) -> String { UtxoJsonRpcClientInfo::client_info(self) } @@ -732,10 +741,10 @@ impl JsonRpcClient for NativeClientImpl { self.event_handlers.on_outgoing_request(request_body.as_bytes()); let uri = self.uri.clone(); - + let auth = self.auth.clone(); let http_request = try_f!(Request::builder() .method("POST") - .header(AUTHORIZATION, self.auth.clone()) + .header(AUTHORIZATION, auth) .uri(uri.clone()) .body(Vec::from(request_body)) .map_err(|e| JsonRpcErrorType::InvalidRequest(e.to_string()))); @@ -828,7 +837,7 @@ impl UtxoRpcClientOps for NativeClient { Box::new(rpc_func!(self, "sendrawtransaction", tx).map_to_mm_fut(UtxoRpcError::from)) } - fn blockchain_scripthash_subscribe(&self, _scripthash: String) -> UtxoRpcFut { + fn blockchain_scripthash_subscribe_using(&self, _: &str, _scripthash: String) -> UtxoRpcFut { Box::new(futures01::future::err( UtxoRpcError::Internal("blockchain_scripthash_subscribe` is not supported for Native Clients".to_owned()) .into(), @@ -1225,1858 +1234,6 @@ impl NativeClientImpl { } } -#[derive(Clone, Debug, Deserialize)] -pub struct ElectrumUnspent { - pub height: Option, - pub tx_hash: H256Json, - pub tx_pos: u32, - pub value: u64, -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(untagged)] -pub enum ElectrumNonce { - Number(u64), - Hash(H256Json), -} - -#[allow(clippy::from_over_into)] -impl Into for ElectrumNonce { - fn into(self) -> BlockHeaderNonce { - match self { - ElectrumNonce::Number(n) => BlockHeaderNonce::U32(n as u32), - ElectrumNonce::Hash(h) => BlockHeaderNonce::H256(h.into()), - } - } -} - -#[derive(Debug, Deserialize)] -pub struct ElectrumBlockHeadersRes { - pub count: u64, - pub hex: BytesJson, - #[allow(dead_code)] - max: u64, -} - -/// The block header compatible with Electrum 1.2 -#[derive(Clone, Debug, Deserialize)] -pub struct ElectrumBlockHeaderV12 { - pub bits: u64, - pub block_height: u64, - pub merkle_root: H256Json, - pub nonce: ElectrumNonce, - pub prev_block_hash: H256Json, - pub timestamp: u64, - pub version: u64, -} - -impl ElectrumBlockHeaderV12 { - fn as_block_header(&self) -> BlockHeader { - BlockHeader { - version: self.version as u32, - previous_header_hash: self.prev_block_hash.into(), - merkle_root_hash: self.merkle_root.into(), - claim_trie_root: None, - hash_final_sapling_root: None, - time: self.timestamp as u32, - bits: BlockHeaderBits::U32(self.bits as u32), - nonce: self.nonce.clone().into(), - solution: None, - aux_pow: None, - prog_pow: None, - mtp_pow: None, - is_verus: false, - hash_state_root: None, - hash_utxo_root: None, - prevout_stake: None, - vch_block_sig_dlgt: None, - n_height: None, - n_nonce_u64: None, - mix_hash: None, - } - } - - #[inline] - pub fn as_hex(&self) -> String { - let block_header = self.as_block_header(); - let serialized = serialize(&block_header); - hex::encode(serialized) - } - - #[inline] - pub fn hash(&self) -> H256Json { - let block_header = self.as_block_header(); - BlockHeader::hash(&block_header).into() - } -} - -/// The block header compatible with Electrum 1.4 -#[derive(Clone, Debug, Deserialize)] -pub struct ElectrumBlockHeaderV14 { - pub height: u64, - pub hex: BytesJson, -} - -impl ElectrumBlockHeaderV14 { - pub fn hash(&self) -> H256Json { self.hex.clone().into_vec()[..].into() } -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(untagged)] -pub enum ElectrumBlockHeader { - V12(ElectrumBlockHeaderV12), - V14(ElectrumBlockHeaderV14), -} - -/// The merkle branch of a confirmed transaction -#[derive(Clone, Debug, Deserialize)] -pub struct TxMerkleBranch { - pub merkle: Vec, - pub block_height: u64, - pub pos: usize, -} - -#[derive(Clone)] -pub struct ConfirmedTransactionInfo { - pub tx: UtxoTx, - pub header: BlockHeader, - pub index: u64, - pub height: u64, -} - -#[derive(Debug, PartialEq)] -pub struct BestBlock { - pub height: u64, - pub hash: H256Json, -} - -impl From for BestBlock { - fn from(block_header: ElectrumBlockHeader) -> Self { - BestBlock { - height: block_header.block_height(), - hash: block_header.block_hash(), - } - } -} - -#[allow(clippy::upper_case_acronyms)] -#[derive(Debug, Deserialize, Serialize)] -pub enum EstimateFeeMode { - ECONOMICAL, - CONSERVATIVE, - UNSET, -} - -impl ElectrumBlockHeader { - pub fn block_height(&self) -> u64 { - match self { - ElectrumBlockHeader::V12(h) => h.block_height, - ElectrumBlockHeader::V14(h) => h.height, - } - } - - fn block_hash(&self) -> H256Json { - match self { - ElectrumBlockHeader::V12(h) => h.hash(), - ElectrumBlockHeader::V14(h) => h.hash(), - } - } -} - -#[derive(Debug, Deserialize)] -pub struct ElectrumTxHistoryItem { - pub height: i64, - pub tx_hash: H256Json, - pub fee: Option, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct ElectrumBalance { - pub(crate) confirmed: i128, - pub(crate) unconfirmed: i128, -} - -impl ElectrumBalance { - #[inline] - pub fn to_big_decimal(&self, decimals: u8) -> BigDecimal { - let balance_sat = BigInt::from(self.confirmed) + BigInt::from(self.unconfirmed); - BigDecimal::from(balance_sat) / BigDecimal::from(10u64.pow(decimals as u32)) - } -} - -#[inline] -fn sha_256(input: &[u8]) -> Vec { - let mut sha = Sha256::new(); - sha.update(input); - sha.finalize().to_vec() -} - -#[inline] -pub fn electrum_script_hash(script: &[u8]) -> Vec { - let mut result = sha_256(script); - result.reverse(); - result -} - -#[derive(Debug, Deserialize, Serialize)] -/// Deserializable Electrum protocol version representation for RPC -/// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-methods.html#server.version -pub struct ElectrumProtocolVersion { - pub server_software_version: String, - pub protocol_version: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -/// Electrum request RPC representation -pub struct ElectrumRpcRequest { - pub url: String, - #[serde(default)] - pub protocol: ElectrumProtocol, - #[serde(default)] - pub disable_cert_verification: bool, -} - -/// Electrum client configuration -#[allow(clippy::upper_case_acronyms)] -#[cfg(not(target_arch = "wasm32"))] -#[derive(Clone, Debug, Serialize)] -enum ElectrumConfig { - TCP, - SSL { dns_name: String, skip_validation: bool }, -} - -/// Electrum client configuration -#[allow(clippy::upper_case_acronyms)] -#[cfg(target_arch = "wasm32")] -#[derive(Clone, Debug, Serialize)] -enum ElectrumConfig { - WS, - WSS, -} - -fn addr_to_socket_addr(input: &str) -> Result { - let mut addr = match input.to_socket_addrs() { - Ok(a) => a, - Err(e) => return ERR!("{} resolve error {:?}", input, e), - }; - match addr.next() { - Some(a) => Ok(a), - None => ERR!("{} resolved to None.", input), - } -} - -#[cfg(not(target_arch = "wasm32"))] -fn server_name_from_domain(dns_name: &str) -> Result { - match ServerName::try_from(dns_name) { - Ok(dns_name) if matches!(dns_name, ServerName::DnsName(_)) => Ok(dns_name), - _ => ERR!("Couldn't parse DNS name from '{}'", dns_name), - } -} - -/// Attempts to process the request (parse url, etc), build up the config and create new electrum connection -/// The function takes `abortable_system` that will be used to spawn Electrum's related futures. -#[cfg(not(target_arch = "wasm32"))] -pub fn spawn_electrum( - req: &ElectrumRpcRequest, - event_handlers: Vec, - scripthash_notification_sender: &ScripthashNotificationSender, - abortable_system: AbortableQueue, -) -> Result { - let config = match req.protocol { - ElectrumProtocol::TCP => ElectrumConfig::TCP, - ElectrumProtocol::SSL => { - let uri: Uri = try_s!(req.url.parse()); - let host = uri - .host() - .ok_or(ERRL!("Couldn't retrieve host from addr {}", req.url))?; - - try_s!(server_name_from_domain(host)); - - ElectrumConfig::SSL { - dns_name: host.into(), - skip_validation: req.disable_cert_verification, - } - }, - ElectrumProtocol::WS | ElectrumProtocol::WSS => { - return ERR!("'ws' and 'wss' protocols are not supported yet. Consider using 'TCP' or 'SSL'") - }, - }; - - Ok(electrum_connect( - req.url.clone(), - config, - event_handlers, - scripthash_notification_sender, - abortable_system, - )) -} - -/// Attempts to process the request (parse url, etc), build up the config and create new electrum connection -/// The function takes `abortable_system` that will be used to spawn Electrum's related futures. -#[cfg(target_arch = "wasm32")] -pub fn spawn_electrum( - req: &ElectrumRpcRequest, - event_handlers: Vec, - scripthash_notification_sender: &ScripthashNotificationSender, - abortable_system: AbortableQueue, -) -> Result { - let mut url = req.url.clone(); - let uri: Uri = try_s!(req.url.parse()); - - if uri.scheme().is_some() { - return ERR!( - "There has not to be a scheme in the url: {}. \ - 'ws://' scheme is used by default. \ - Consider using 'protocol: \"WSS\"' in the electrum request to switch to the 'wss://' scheme.", - url - ); - } - - let config = match req.protocol { - ElectrumProtocol::WS => { - url.insert_str(0, "ws://"); - ElectrumConfig::WS - }, - ElectrumProtocol::WSS => { - url.insert_str(0, "wss://"); - ElectrumConfig::WSS - }, - ElectrumProtocol::TCP | ElectrumProtocol::SSL => { - return ERR!("'TCP' and 'SSL' are not supported in a browser. Please use 'WS' or 'WSS' protocols"); - }, - }; - - Ok(electrum_connect( - url, - config, - event_handlers, - scripthash_notification_sender, - abortable_system, - )) -} - -/// Represents the active Electrum connection to selected address -pub struct ElectrumConnection { - /// The client connected to this SocketAddr - addr: String, - /// Configuration - #[allow(dead_code)] - config: ElectrumConfig, - /// The Sender forwarding requests to writing part of underlying stream - tx: Arc>>>>, - /// Responses are stored here - responses: JsonRpcPendingRequestsShared, - /// Selected protocol version. The value is initialized after the server.version RPC call. - protocol_version: AsyncMutex>, - /// This spawner is used to spawn Electrum's related futures that should be aborted on coin deactivation. - /// and on [`MmArc::stop`]. - /// This field is not used directly, but it holds all abort handles of futures spawned at `electrum_connect`. - /// - /// Please also note that this abortable system is a subsystem of [`ElectrumClientImpl::abortable_system`]. - /// For more info see [`ElectrumClientImpl::add_server`]. - _abortable_system: AbortableQueue, -} - -impl ElectrumConnection { - async fn is_connected(&self) -> bool { self.tx.lock().await.is_some() } - - async fn set_protocol_version(&self, version: f32) { self.protocol_version.lock().await.replace(version); } - - async fn reset_protocol_version(&self) { *self.protocol_version.lock().await = None; } -} - -#[derive(Debug)] -struct ConcurrentRequestState { - is_running: bool, - subscribers: Vec>, -} - -impl ConcurrentRequestState { - fn new() -> Self { - ConcurrentRequestState { - is_running: false, - subscribers: Vec::new(), - } - } -} - -#[derive(Debug)] -pub struct ConcurrentRequestMap { - inner: AsyncMutex>>, -} - -impl Default for ConcurrentRequestMap { - fn default() -> Self { - ConcurrentRequestMap { - inner: AsyncMutex::new(HashMap::new()), - } - } -} - -impl ConcurrentRequestMap { - pub fn new() -> ConcurrentRequestMap { ConcurrentRequestMap::default() } - - async fn wrap_request(&self, request_arg: K, request_fut: RpcRes) -> Result { - let mut map = self.inner.lock().await; - let state = map - .entry(request_arg.clone()) - .or_insert_with(ConcurrentRequestState::new); - if state.is_running { - let (tx, rx) = async_oneshot::channel(); - state.subscribers.push(tx); - // drop here to avoid holding the lock during await - drop(map); - rx.await.unwrap() - } else { - // drop here to avoid holding the lock during await - drop(map); - let request_res = request_fut.compat().await; - let mut map = self.inner.lock().await; - let state = map.get_mut(&request_arg).unwrap(); - for sub in state.subscribers.drain(..) { - if sub.send(request_res.clone()).is_err() { - warn!("subscriber is dropped"); - } - } - state.is_running = false; - request_res - } - } -} - -#[derive(Debug)] -pub struct ElectrumClientImpl { - coin_ticker: String, - connections: AsyncMutex>, - next_id: AtomicU64, - event_handlers: Vec, - protocol_version: OrdRange, - get_balance_concurrent_map: ConcurrentRequestMap, - list_unspent_concurrent_map: ConcurrentRequestMap>, - block_headers_storage: BlockHeaderStorage, - /// This spawner is used to spawn Electrum's related futures that should be aborted on coin deactivation, - /// and on [`MmArc::stop`]. - /// - /// Please also note that this abortable system is a subsystem of [`UtxoCoinFields::abortable_system`]. - abortable_system: AbortableQueue, - negotiate_version: bool, - /// This is used for balance event streaming implementation for UTXOs. - /// If balance event streaming isn't enabled, this value will always be `None`; otherwise, - /// it will be used for sending scripthash messages to trigger re-connections, re-fetching the balances, etc. - pub(crate) scripthash_notification_sender: ScripthashNotificationSender, -} - -async fn electrum_request_multi( - client: ElectrumClient, - request: JsonRpcRequestEnum, -) -> Result<(JsonRpcRemoteAddr, JsonRpcResponseEnum), JsonRpcErrorType> { - let mut futures = vec![]; - let connections = client.connections.lock().await; - for (i, connection) in connections.iter().enumerate() { - if client.negotiate_version && connection.protocol_version.lock().await.is_none() { - continue; - } - - let connection_addr = connection.addr.clone(); - let json = json::to_string(&request).map_err(|e| JsonRpcErrorType::InvalidRequest(e.to_string()))?; - if let Some(tx) = &*connection.tx.lock().await { - let fut = electrum_request( - json, - request.rpc_id(), - tx.clone(), - connection.responses.clone(), - ELECTRUM_TIMEOUT / (connections.len() - i) as u64, - ) - .map(|response| (JsonRpcRemoteAddr(connection_addr), response)); - futures.push(fut) - } - } - drop(connections); - - if futures.is_empty() { - return Err(JsonRpcErrorType::Transport( - "All electrums are currently disconnected".to_string(), - )); - } - - if let JsonRpcRequestEnum::Single(single) = &request { - if single.method == "server.ping" { - // server.ping must be sent to all servers to keep all connections alive - return select_ok(futures).map(|(result, _)| result).compat().await; - } - } - - let (res, no_of_failed_requests) = select_ok_sequential(futures) - .compat() - .await - .map_err(|e| JsonRpcErrorType::Transport(format!("{:?}", e)))?; - client.rotate_servers(no_of_failed_requests).await; - - Ok(res) -} - -async fn electrum_request_to( - client: ElectrumClient, - request: JsonRpcRequestEnum, - to_addr: String, -) -> Result<(JsonRpcRemoteAddr, JsonRpcResponseEnum), JsonRpcErrorType> { - let (tx, responses) = { - let connections = client.connections.lock().await; - let connection = connections - .iter() - .find(|c| c.addr == to_addr) - .ok_or_else(|| JsonRpcErrorType::Internal(format!("Unknown destination address {}", to_addr)))?; - let responses = connection.responses.clone(); - let tx = { - match &*connection.tx.lock().await { - Some(tx) => tx.clone(), - None => { - return Err(JsonRpcErrorType::Transport(format!( - "Connection {} is not established yet", - to_addr - ))) - }, - } - }; - (tx, responses) - }; - let json = json::to_string(&request).map_err(|err| JsonRpcErrorType::InvalidRequest(err.to_string()))?; - let response = electrum_request(json, request.rpc_id(), tx, responses, ELECTRUM_TIMEOUT) - .compat() - .await?; - Ok((JsonRpcRemoteAddr(to_addr.to_owned()), response)) -} - -impl ElectrumClientImpl { - pub fn spawner(&self) -> abortable_queue::WeakSpawner { self.abortable_system.weak_spawner() } - - /// Create an Electrum connection and spawn a green thread actor to handle it. - pub async fn add_server(&self, req: &ElectrumRpcRequest) -> Result<(), String> { - let subsystem = try_s!(self.abortable_system.create_subsystem()); - let connection = try_s!(spawn_electrum( - req, - self.event_handlers.clone(), - &self.scripthash_notification_sender, - subsystem, - )); - self.connections.lock().await.push(connection); - Ok(()) - } - - /// Remove an Electrum connection and stop corresponding spawned actor. - pub async fn remove_server(&self, server_addr: &str) -> Result<(), String> { - let mut connections = self.connections.lock().await; - // do not use retain, we would have to return an error if we did not find connection by the passd address - let pos = connections - .iter() - .position(|con| con.addr == server_addr) - .ok_or(ERRL!("Unknown electrum address {}", server_addr))?; - // shutdown_tx will be closed immediately on the connection drop - connections.remove(pos); - Ok(()) - } - - /// Moves the Electrum servers that fail in a multi request to the end. - pub async fn rotate_servers(&self, no_of_rotations: usize) { - let mut connections = self.connections.lock().await; - connections.rotate_left(no_of_rotations); - } - - /// Check if one of the spawned connections is connected. - pub async fn is_connected(&self) -> bool { - for connection in self.connections.lock().await.iter() { - if connection.is_connected().await { - return true; - } - } - false - } - - /// Check if all connections have been removed. - pub async fn is_connections_pool_empty(&self) -> bool { self.connections.lock().await.is_empty() } - - pub async fn count_connections(&self) -> usize { self.connections.lock().await.len() } - - /// Check if the protocol version was checked for one of the spawned connections. - pub async fn is_protocol_version_checked(&self) -> bool { - for connection in self.connections.lock().await.iter() { - if connection.protocol_version.lock().await.is_some() { - return true; - } - } - false - } - - /// Set the protocol version for the specified server. - pub async fn set_protocol_version(&self, server_addr: &str, version: f32) -> Result<(), String> { - let connections = self.connections.lock().await; - let con = connections - .iter() - .find(|con| con.addr == server_addr) - .ok_or(ERRL!("Unknown electrum address {}", server_addr))?; - con.set_protocol_version(version).await; - - if let Some(sender) = &self.scripthash_notification_sender { - sender - .unbounded_send(ScripthashNotification::RefreshSubscriptions) - .map_err(|e| ERRL!("Failed sending scripthash message. {}", e))?; - } - - Ok(()) - } - - /// Reset the protocol version for the specified server. - pub async fn reset_protocol_version(&self, server_addr: &str) -> Result<(), String> { - let connections = self.connections.lock().await; - let con = connections - .iter() - .find(|con| con.addr == server_addr) - .ok_or(ERRL!("Unknown electrum address {}", server_addr))?; - con.reset_protocol_version().await; - Ok(()) - } - - /// Get available protocol versions. - pub fn protocol_version(&self) -> &OrdRange { &self.protocol_version } - - /// Get block headers storage. - pub fn block_headers_storage(&self) -> &BlockHeaderStorage { &self.block_headers_storage } -} - -#[derive(Clone, Debug)] -pub struct ElectrumClient(pub Arc); -impl Deref for ElectrumClient { - type Target = ElectrumClientImpl; - fn deref(&self) -> &ElectrumClientImpl { &self.0 } -} - -const BLOCKCHAIN_HEADERS_SUB_ID: &str = "blockchain.headers.subscribe"; - -const BLOCKCHAIN_SCRIPTHASH_SUB_ID: &str = "blockchain.scripthash.subscribe"; - -impl UtxoJsonRpcClientInfo for ElectrumClient { - fn coin_name(&self) -> &str { self.coin_ticker.as_str() } -} - -impl JsonRpcClient for ElectrumClient { - fn version(&self) -> &'static str { "2.0" } - - fn next_id(&self) -> String { self.next_id.fetch_add(1, AtomicOrdering::Relaxed).to_string() } - - fn client_info(&self) -> String { UtxoJsonRpcClientInfo::client_info(self) } - - fn transport(&self, request: JsonRpcRequestEnum) -> JsonRpcResponseFut { - Box::new(electrum_request_multi(self.clone(), request).boxed().compat()) - } -} - -impl JsonRpcBatchClient for ElectrumClient {} - -impl JsonRpcMultiClient for ElectrumClient { - fn transport_exact(&self, to_addr: String, request: JsonRpcRequestEnum) -> JsonRpcResponseFut { - Box::new(electrum_request_to(self.clone(), request, to_addr).boxed().compat()) - } -} - -impl ElectrumClient { - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#server-ping - pub fn server_ping(&self) -> RpcRes<()> { rpc_func!(self, "server.ping") } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#server-version - pub fn server_version( - &self, - server_address: &str, - client_name: &str, - version: &OrdRange, - ) -> RpcRes { - let protocol_version: Vec = version.flatten().into_iter().map(|v| format!("{}", v)).collect(); - rpc_func_from!(self, server_address, "server.version", client_name, protocol_version) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe - pub fn get_block_count_from(&self, server_address: &str) -> RpcRes { - Box::new( - rpc_func_from!(self, server_address, BLOCKCHAIN_HEADERS_SUB_ID) - .map(|r: ElectrumBlockHeader| r.block_height()), - ) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-headers - pub fn get_block_headers_from( - &self, - server_address: &str, - start_height: u64, - count: NonZeroU64, - ) -> RpcRes { - rpc_func_from!(self, server_address, "blockchain.block.headers", start_height, count) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-listunspent - /// It can return duplicates sometimes: https://github.com/artemii235/SuperNET/issues/269 - /// We should remove them to build valid transactions - pub fn scripthash_list_unspent(&self, hash: &str) -> RpcRes> { - let request_fut = Box::new(rpc_func!(self, "blockchain.scripthash.listunspent", hash).and_then( - move |unspents: Vec| { - let mut map: HashMap<(H256Json, u32), bool> = HashMap::new(); - let unspents = unspents - .into_iter() - .filter(|unspent| match map.entry((unspent.tx_hash, unspent.tx_pos)) { - Entry::Occupied(_) => false, - Entry::Vacant(e) => { - e.insert(true); - true - }, - }) - .collect(); - Ok(unspents) - }, - )); - let arc = self.clone(); - let hash = hash.to_owned(); - let fut = async move { arc.list_unspent_concurrent_map.wrap_request(hash, request_fut).await }; - Box::new(fut.boxed().compat()) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-listunspent - /// It can return duplicates sometimes: https://github.com/artemii235/SuperNET/issues/269 - /// We should remove them to build valid transactions. - /// Please note the function returns `ScriptHashUnspents` elements in the same order in which they were requested. - pub fn scripthash_list_unspent_batch(&self, hashes: Vec) -> RpcRes> { - let requests = hashes - .iter() - .map(|hash| rpc_req!(self, "blockchain.scripthash.listunspent", hash)); - Box::new(self.batch_rpc(requests).map(move |unspents: Vec| { - unspents - .into_iter() - .map(|hash_unspents| { - hash_unspents - .into_iter() - .unique_by(|unspent| (unspent.tx_hash, unspent.tx_pos)) - .collect::>() - }) - .collect() - })) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-get-history - pub fn scripthash_get_history(&self, hash: &str) -> RpcRes { - rpc_func!(self, "blockchain.scripthash.get_history", hash) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-get-history - /// Requests history of the `hashes` in a batch and returns them in the same order they were requested. - pub fn scripthash_get_history_batch(&self, hashes: I) -> RpcRes> - where - I: IntoIterator, - { - let requests = hashes - .into_iter() - .map(|hash| rpc_req!(self, "blockchain.scripthash.get_history", hash)); - self.batch_rpc(requests) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-gethistory - pub fn scripthash_get_balance(&self, hash: &str) -> RpcRes { - let arc = self.clone(); - let hash = hash.to_owned(); - let fut = async move { - let request = rpc_func!(arc, "blockchain.scripthash.get_balance", &hash); - arc.get_balance_concurrent_map.wrap_request(hash, request).await - }; - Box::new(fut.boxed().compat()) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-gethistory - /// Requests balances in a batch and returns them in the same order they were requested. - pub fn scripthash_get_balances(&self, hashes: I) -> RpcRes> - where - I: IntoIterator, - { - let requests = hashes - .into_iter() - .map(|hash| rpc_req!(self, "blockchain.scripthash.get_balance", &hash)); - self.batch_rpc(requests) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe - pub fn blockchain_headers_subscribe(&self) -> RpcRes { - rpc_func!(self, BLOCKCHAIN_HEADERS_SUB_ID) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-broadcast - pub fn blockchain_transaction_broadcast(&self, tx: BytesJson) -> RpcRes { - rpc_func!(self, "blockchain.transaction.broadcast", tx) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-estimatefee - /// It is recommended to set n_blocks as low as possible. - /// However, in some cases, n_blocks = 1 leads to an unreasonably high fee estimation. - /// https://github.com/KomodoPlatform/atomicDEX-API/issues/656#issuecomment-743759659 - pub fn estimate_fee(&self, mode: &Option, n_blocks: u32) -> UtxoRpcFut { - match mode { - Some(m) => { - Box::new(rpc_func!(self, "blockchain.estimatefee", n_blocks, m).map_to_mm_fut(UtxoRpcError::from)) - }, - None => Box::new(rpc_func!(self, "blockchain.estimatefee", n_blocks).map_to_mm_fut(UtxoRpcError::from)), - } - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-header - pub fn blockchain_block_header(&self, height: u64) -> RpcRes { - rpc_func!(self, "blockchain.block.header", height) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-headers - pub fn blockchain_block_headers(&self, start_height: u64, count: NonZeroU64) -> RpcRes { - rpc_func!(self, "blockchain.block.headers", start_height, count) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get-merkle - pub fn blockchain_transaction_get_merkle(&self, txid: H256Json, height: u64) -> RpcRes { - rpc_func!(self, "blockchain.transaction.get_merkle", txid, height) - } - - // get_tx_height_from_rpc is costly since it loops through history after requesting the whole history of the script pubkey - // This method should always be used if the block headers are saved to the DB - async fn get_tx_height_from_storage(&self, tx: &UtxoTx) -> Result> { - let tx_hash = tx.hash().reversed(); - let blockhash = self.get_verbose_transaction(&tx_hash.into()).compat().await?.blockhash; - Ok(self - .block_headers_storage() - .get_block_height_by_hash(blockhash.into()) - .await? - .ok_or_else(|| { - GetTxHeightError::HeightNotFound(format!( - "Transaction block header is not found in storage for {}", - self.0.coin_ticker - )) - })? - .try_into()?) - } - - // get_tx_height_from_storage is always preferred to be used instead of this, but if there is no headers in storage (storing headers is not enabled) - // this function can be used instead - async fn get_tx_height_from_rpc(&self, tx: &UtxoTx) -> Result { - for output in tx.outputs.clone() { - let script_pubkey_str = hex::encode(electrum_script_hash(&output.script_pubkey)); - if let Ok(history) = self.scripthash_get_history(script_pubkey_str.as_str()).compat().await { - if let Some(item) = history - .into_iter() - .find(|item| item.tx_hash.reversed() == H256Json(*tx.hash()) && item.height > 0) - { - return Ok(item.height as u64); - } - } - } - Err(GetTxHeightError::HeightNotFound(format!( - "Couldn't find height through electrum for {}", - self.coin_ticker - ))) - } - - async fn block_header_from_storage(&self, height: u64) -> Result> { - self.block_headers_storage() - .get_block_header(height) - .await? - .ok_or_else(|| { - GetBlockHeaderError::Internal(format!("Header not found in storage for {}", self.coin_ticker)).into() - }) - } - - async fn block_header_from_storage_or_rpc(&self, height: u64) -> Result> { - match self.block_header_from_storage(height).await { - Ok(h) => Ok(h), - Err(_) => Ok(deserialize( - self.blockchain_block_header(height).compat().await?.as_slice(), - )?), - } - } - - pub async fn get_confirmed_tx_info_from_rpc( - &self, - tx: &UtxoTx, - ) -> Result { - let height = self.get_tx_height_from_rpc(tx).await?; - - let merkle_branch = self - .blockchain_transaction_get_merkle(tx.hash().reversed().into(), height) - .compat() - .await?; - - let header = deserialize(self.blockchain_block_header(height).compat().await?.as_slice())?; - - Ok(ConfirmedTransactionInfo { - tx: tx.clone(), - header, - index: merkle_branch.pos as u64, - height, - }) - } - - pub async fn get_merkle_and_validated_header( - &self, - tx: &UtxoTx, - ) -> Result<(TxMerkleBranch, BlockHeader, u64), MmError> { - let height = self.get_tx_height_from_storage(tx).await?; - - let merkle_branch = self - .blockchain_transaction_get_merkle(tx.hash().reversed().into(), height) - .compat() - .await - .map_to_mm(|err| SPVError::UnableToGetMerkle { - coin: self.coin_ticker.clone(), - err: err.to_string(), - })?; - - let header = self.block_header_from_storage(height).await?; - - Ok((merkle_branch, header, height)) - } -} - -#[cfg_attr(test, mockable)] -impl ElectrumClient { - pub fn retrieve_headers_from( - &self, - server_address: &str, - from_height: u64, - to_height: u64, - ) -> UtxoRpcFut<(HashMap, Vec)> { - let coin_name = self.coin_ticker.clone(); - if from_height == 0 || to_height < from_height { - return Box::new(futures01::future::err( - UtxoRpcError::Internal("Invalid values for from/to parameters".to_string()).into(), - )); - } - let count: NonZeroU64 = match (to_height - from_height + 1).try_into() { - Ok(c) => c, - Err(e) => return Box::new(futures01::future::err(UtxoRpcError::Internal(e.to_string()).into())), - }; - Box::new( - self.get_block_headers_from(server_address, from_height, count) - .map_to_mm_fut(UtxoRpcError::from) - .and_then(move |headers| { - let (block_registry, block_headers) = { - if headers.count == 0 { - return MmError::err(UtxoRpcError::Internal("No headers available".to_string())); - } - let len = CompactInteger::from(headers.count); - let mut serialized = serialize(&len).take(); - serialized.extend(headers.hex.0.into_iter()); - drop_mutability!(serialized); - let mut reader = - Reader::new_with_coin_variant(serialized.as_slice(), coin_name.as_str().into()); - let maybe_block_headers = reader.read_list::(); - let block_headers = match maybe_block_headers { - Ok(headers) => headers, - Err(e) => return MmError::err(UtxoRpcError::InvalidResponse(format!("{:?}", e))), - }; - let mut block_registry: HashMap = HashMap::with_capacity(block_headers.len()); - let mut starting_height = from_height; - for block_header in &block_headers { - block_registry.insert(starting_height, block_header.clone()); - starting_height += 1; - } - (block_registry, block_headers) - }; - Ok((block_registry, block_headers)) - }), - ) - } - - pub(crate) fn get_servers_with_latest_block_count(&self) -> UtxoRpcFut<(Vec, u64)> { - let selfi = self.clone(); - let fut = async move { - let connections = selfi.connections.lock().await; - let futures = connections - .iter() - .map(|connection| { - let addr = connection.addr.clone(); - selfi - .get_block_count_from(&addr) - .map(|response| (addr, response)) - .compat() - }) - .collect::>(); - drop(connections); - - let responses = join_all(futures).await; - - // First, we use filter_map to get rid of any errors and collect the - // server addresses and block counts into two vectors - let (responding_servers, block_counts_from_all_servers): (Vec<_>, Vec<_>) = - responses.clone().into_iter().filter_map(|res| res.ok()).unzip(); - - // Next, we use max to find the maximum block count from all servers - if let Some(max_block_count) = block_counts_from_all_servers.clone().iter().max() { - // Then, we use filter and collect to get the servers that have the maximum block count - let servers_with_max_count: Vec<_> = responding_servers - .into_iter() - .zip(block_counts_from_all_servers) - .filter(|(_, count)| count == max_block_count) - .map(|(addr, _)| addr) - .collect(); - - // Finally, we return a tuple of servers with max count and the max count - return Ok((servers_with_max_count, *max_block_count)); - } - - return Err(MmError::new(UtxoRpcError::Internal(format!( - "Couldn't get block count from any server for {}, responses: {:?}", - &selfi.coin_ticker, responses - )))); - }; - - Box::new(fut.boxed().compat()) - } -} - -// if mockable is placed before async_trait there is `munmap_chunk(): invalid pointer` error on async fn mocking attempt -#[async_trait] -#[cfg_attr(test, mockable)] -impl UtxoRpcClientOps for ElectrumClient { - fn list_unspent(&self, address: &Address, _decimals: u8) -> UtxoRpcFut> { - let mut output_scripts = vec![try_f!(output_script(address))]; - - // If the plain pubkey is available, fetch the UTXOs found in P2PK outputs as well (if any). - if let Some(pubkey) = address.pubkey() { - let p2pk_output_script = output_script_p2pk(pubkey); - output_scripts.push(p2pk_output_script); - } - - let this = self.clone(); - let fut = async move { - let hashes = output_scripts - .iter() - .map(|s| hex::encode(electrum_script_hash(s))) - .collect(); - let unspents = this.scripthash_list_unspent_batch(hashes).compat().await?; - - let unspents = unspents - .into_iter() - .zip(output_scripts) - .flat_map(|(unspents, output_script)| { - unspents - .into_iter() - .map(move |unspent| UnspentInfo::from_electrum(unspent, output_script.clone())) - }) - .collect(); - Ok(unspents) - }; - - Box::new(fut.boxed().compat()) - } - - fn list_unspent_group(&self, addresses: Vec
, _decimals: u8) -> UtxoRpcFut { - let output_scripts = try_f!(addresses - .iter() - .map(output_script) - .collect::, keys::Error>>()); - - let this = self.clone(); - let fut = async move { - let hashes = output_scripts - .iter() - .map(|s| hex::encode(electrum_script_hash(s))) - .collect(); - let unspents = this.scripthash_list_unspent_batch(hashes).compat().await?; - - let unspents: Vec> = unspents - .into_iter() - .zip(output_scripts) - .map(|(unspents, output_script)| { - unspents - .into_iter() - .map(|unspent| UnspentInfo::from_electrum(unspent, output_script.clone())) - .collect() - }) - .collect(); - - let unspent_map = addresses - .into_iter() - // `scripthash_list_unspent_batch` returns `ScriptHashUnspents` elements in the same order in which they were requested. - // So we can zip `addresses` and `unspents` into one iterator. - .zip(unspents) - .collect(); - Ok(unspent_map) - }; - Box::new(fut.boxed().compat()) - } - - fn send_transaction(&self, tx: &UtxoTx) -> UtxoRpcFut { - let bytes = if tx.has_witness() { - BytesJson::from(serialize_with_flags(tx, SERIALIZE_TRANSACTION_WITNESS)) - } else { - BytesJson::from(serialize(tx)) - }; - Box::new( - self.blockchain_transaction_broadcast(bytes) - .map_to_mm_fut(UtxoRpcError::from), - ) - } - - fn send_raw_transaction(&self, tx: BytesJson) -> UtxoRpcFut { - Box::new( - self.blockchain_transaction_broadcast(tx) - .map_to_mm_fut(UtxoRpcError::from), - ) - } - - fn blockchain_scripthash_subscribe(&self, scripthash: String) -> UtxoRpcFut { - Box::new(rpc_func!(self, BLOCKCHAIN_SCRIPTHASH_SUB_ID, scripthash).map_to_mm_fut(UtxoRpcError::from)) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get - /// returns transaction bytes by default - fn get_transaction_bytes(&self, txid: &H256Json) -> UtxoRpcFut { - let verbose = false; - Box::new(rpc_func!(self, "blockchain.transaction.get", txid, verbose).map_to_mm_fut(UtxoRpcError::from)) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get - /// returns verbose transaction by default - fn get_verbose_transaction(&self, txid: &H256Json) -> UtxoRpcFut { - let verbose = true; - Box::new(rpc_func!(self, "blockchain.transaction.get", txid, verbose).map_to_mm_fut(UtxoRpcError::from)) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get - /// Returns verbose transactions in a batch. - fn get_verbose_transactions(&self, tx_ids: &[H256Json]) -> UtxoRpcFut> { - let verbose = true; - let requests = tx_ids - .iter() - .map(|txid| rpc_req!(self, "blockchain.transaction.get", txid, verbose)); - Box::new(self.batch_rpc(requests).map_to_mm_fut(UtxoRpcError::from)) - } - - fn get_block_count(&self) -> UtxoRpcFut { - Box::new( - self.blockchain_headers_subscribe() - .map(|r| r.block_height()) - .map_to_mm_fut(UtxoRpcError::from), - ) - } - - fn display_balance(&self, address: Address, decimals: u8) -> RpcRes { - let output_script = try_f!(output_script(&address).map_err(|err| JsonRpcError::new( - UtxoJsonRpcClientInfo::client_info(self), - rpc_req!(self, "blockchain.scripthash.get_balance").into(), - JsonRpcErrorType::Internal(err.to_string()) - ))); - let mut hashes = vec![hex::encode(electrum_script_hash(&output_script))]; - - // If the plain pubkey is available, fetch the balance found in P2PK output as well (if any). - if let Some(pubkey) = address.pubkey() { - let p2pk_output_script = output_script_p2pk(pubkey); - hashes.push(hex::encode(electrum_script_hash(&p2pk_output_script))); - } - - let this = self.clone(); - let fut = async move { - Ok(this - .scripthash_get_balances(hashes) - .compat() - .await? - .into_iter() - .fold(BigDecimal::from(0), |sum, electrum_balance| { - sum + electrum_balance.to_big_decimal(decimals) - })) - }; - Box::new(fut.boxed().compat()) - } - - fn display_balances(&self, addresses: Vec
, decimals: u8) -> UtxoRpcFut> { - let this = self.clone(); - let fut = async move { - let hashes = addresses - .iter() - .map(|address| { - let output_script = output_script(address)?; - let hash = electrum_script_hash(&output_script); - - Ok(hex::encode(hash)) - }) - .collect::, keys::Error>>()?; - - let electrum_balances = this.scripthash_get_balances(hashes).compat().await?; - let balances = electrum_balances - .into_iter() - // `scripthash_get_balances` returns `ElectrumBalance` elements in the same order in which they were requested. - // So we can zip `addresses` and the balances into one iterator. - .zip(addresses) - .map(|(electrum_balance, address)| (address, electrum_balance.to_big_decimal(decimals))) - .collect(); - Ok(balances) - }; - - Box::new(fut.boxed().compat()) - } - - fn estimate_fee_sat( - &self, - decimals: u8, - _fee_method: &EstimateFeeMethod, - mode: &Option, - n_blocks: u32, - ) -> UtxoRpcFut { - Box::new(self.estimate_fee(mode, n_blocks).map(move |fee| { - if fee > 0.00001 { - (fee * 10.0_f64.powf(decimals as f64)) as u64 - } else { - 1000 - } - })) - } - - fn get_relay_fee(&self) -> RpcRes { rpc_func!(self, "blockchain.relayfee") } - - fn find_output_spend( - &self, - tx_hash: H256, - script_pubkey: &[u8], - vout: usize, - _from_block: BlockHashOrHeight, - tx_hash_algo: TxHashAlgo, - ) -> Box, Error = String> + Send> { - let selfi = self.clone(); - let script_hash = hex::encode(electrum_script_hash(script_pubkey)); - let fut = async move { - let history = try_s!(selfi.scripthash_get_history(&script_hash).compat().await); - - if history.len() < 2 { - return Ok(None); - } - - for item in history.iter() { - let transaction = try_s!(selfi.get_transaction_bytes(&item.tx_hash).compat().await); - - let mut maybe_spend_tx: UtxoTx = - try_s!(deserialize(transaction.as_slice()).map_err(|e| ERRL!("{:?}", e))); - maybe_spend_tx.tx_hash_algo = tx_hash_algo; - drop_mutability!(maybe_spend_tx); - - for (index, input) in maybe_spend_tx.inputs.iter().enumerate() { - if input.previous_output.hash == tx_hash && input.previous_output.index == vout as u32 { - return Ok(Some(SpentOutputInfo { - input: input.clone(), - input_index: index, - spending_tx: maybe_spend_tx, - spent_in_block: BlockHashOrHeight::Height(item.height), - })); - } - } - } - Ok(None) - }; - Box::new(fut.boxed().compat()) - } - - fn get_median_time_past( - &self, - starting_block: u64, - count: NonZeroU64, - coin_variant: CoinVariant, - ) -> UtxoRpcFut { - let from = if starting_block <= count.get() { - 0 - } else { - starting_block - count.get() + 1 - }; - Box::new( - self.blockchain_block_headers(from, count) - .map_to_mm_fut(UtxoRpcError::from) - .and_then(|res| { - if res.count == 0 { - return MmError::err(UtxoRpcError::InvalidResponse("Server returned zero count".to_owned())); - } - let len = CompactInteger::from(res.count); - let mut serialized = serialize(&len).take(); - serialized.extend(res.hex.0.into_iter()); - let mut reader = Reader::new_with_coin_variant(serialized.as_slice(), coin_variant); - let headers = reader.read_list::()?; - let mut timestamps: Vec<_> = headers.into_iter().map(|block| block.time).collect(); - // can unwrap because count is non zero - Ok(median(timestamps.as_mut_slice()).unwrap()) - }), - ) - } - - async fn get_block_timestamp(&self, height: u64) -> Result> { - Ok(self.block_header_from_storage_or_rpc(height).await?.time as u64) - } -} - -#[cfg_attr(test, mockable)] -impl ElectrumClientImpl { - pub fn new( - coin_ticker: String, - event_handlers: Vec, - block_headers_storage: BlockHeaderStorage, - abortable_system: AbortableQueue, - negotiate_version: bool, - scripthash_notification_sender: ScripthashNotificationSender, - ) -> ElectrumClientImpl { - let protocol_version = OrdRange::new(1.2, 1.4).unwrap(); - ElectrumClientImpl { - coin_ticker, - connections: AsyncMutex::new(vec![]), - next_id: 0.into(), - event_handlers, - protocol_version, - get_balance_concurrent_map: ConcurrentRequestMap::new(), - list_unspent_concurrent_map: ConcurrentRequestMap::new(), - block_headers_storage, - abortable_system, - negotiate_version, - scripthash_notification_sender, - } - } - - #[cfg(test)] - pub fn with_protocol_version( - coin_ticker: String, - event_handlers: Vec, - protocol_version: OrdRange, - block_headers_storage: BlockHeaderStorage, - abortable_system: AbortableQueue, - scripthash_notification_sender: ScripthashNotificationSender, - ) -> ElectrumClientImpl { - ElectrumClientImpl { - protocol_version, - ..ElectrumClientImpl::new( - coin_ticker, - event_handlers, - block_headers_storage, - abortable_system, - false, - scripthash_notification_sender, - ) - } - } -} - -/// Helper function casting mpsc::Receiver as Stream. -fn rx_to_stream(rx: mpsc::Receiver>) -> impl Stream, Error = io::Error> { - rx.map_err(|_| panic!("errors not possible on rx")) -} - -async fn electrum_process_json( - raw_json: Json, - arc: &JsonRpcPendingRequestsShared, - scripthash_notification_sender: &ScripthashNotificationSender, -) { - // detect if we got standard JSONRPC response or subscription response as JSONRPC request - #[derive(Deserialize)] - #[serde(untagged)] - enum ElectrumRpcResponseEnum { - /// The subscription response as JSONRPC request. - /// - /// NOTE Because JsonRpcResponse uses default values for each of its field, - /// this variant has to stay at top in this enumeration to be properly deserialized - /// from serde. - SubscriptionNotification(JsonRpcRequest), - /// The standard JSONRPC single response. - SingleResponse(JsonRpcResponse), - /// The batch of standard JSONRPC responses. - BatchResponses(JsonRpcBatchResponse), - } - - let response: ElectrumRpcResponseEnum = match json::from_value(raw_json) { - Ok(res) => res, - Err(e) => { - error!("{}", e); - return; - }, - }; - - let response = match response { - ElectrumRpcResponseEnum::SingleResponse(single) => JsonRpcResponseEnum::Single(single), - ElectrumRpcResponseEnum::BatchResponses(batch) => JsonRpcResponseEnum::Batch(batch), - ElectrumRpcResponseEnum::SubscriptionNotification(req) => { - let id = match req.method.as_ref() { - BLOCKCHAIN_HEADERS_SUB_ID => BLOCKCHAIN_HEADERS_SUB_ID, - BLOCKCHAIN_SCRIPTHASH_SUB_ID => { - let scripthash = match req.params.first() { - Some(t) => t.as_str().unwrap_or_default(), - None => { - debug!("Notification must contain the scripthash value."); - return; - }, - }; - - if let Some(sender) = scripthash_notification_sender { - debug!("Sending scripthash message"); - if let Err(e) = sender.unbounded_send(ScripthashNotification::Triggered(scripthash.to_string())) - { - error!("Failed sending scripthash message. {e}"); - return; - }; - }; - BLOCKCHAIN_SCRIPTHASH_SUB_ID - }, - _ => { - error!("Couldn't get id of request {:?}", req); - return; - }, - }; - JsonRpcResponseEnum::Single(JsonRpcResponse { - id: id.into(), - jsonrpc: "2.0".into(), - result: req.params[0].clone(), - error: Json::Null, - }) - }, - }; - - // the corresponding sender may not exist, receiver may be dropped - // these situations are not considered as errors so we just silently skip them - let mut pending = arc.lock().await; - if let Some(tx) = pending.remove(&response.rpc_id()) { - tx.send(response).ok(); - } -} - -async fn electrum_process_chunk( - chunk: &[u8], - arc: &JsonRpcPendingRequestsShared, - scripthash_notification_sender: ScripthashNotificationSender, -) { - // we should split the received chunk because we can get several responses in 1 chunk. - let split = chunk.split(|item| *item == b'\n'); - for chunk in split { - // split returns empty slice if it ends with separator which is our case - if !chunk.is_empty() { - let raw_json: Json = match json::from_slice(chunk) { - Ok(json) => json, - Err(e) => { - error!("{}", e); - return; - }, - }; - electrum_process_json(raw_json, arc, &scripthash_notification_sender).await - } - } -} - -fn increase_delay(delay: &AtomicU64) { - if delay.load(AtomicOrdering::Relaxed) < 60 { - delay.fetch_add(5, AtomicOrdering::Relaxed); - } -} - -macro_rules! try_loop { - ($e:expr, $addr: ident, $delay: ident) => { - match $e { - Ok(res) => res, - Err(e) => { - error!("{:?} error {:?}", $addr, e); - increase_delay(&$delay); - continue; - }, - } - }; -} - -/// The enum wrapping possible variants of underlying Streams -#[cfg(not(target_arch = "wasm32"))] -#[allow(clippy::large_enum_variant)] -enum ElectrumStream { - Tcp(TcpStream), - Tls(TlsStream), -} - -#[cfg(not(target_arch = "wasm32"))] -impl AsRef for ElectrumStream { - fn as_ref(&self) -> &TcpStream { - match self { - ElectrumStream::Tcp(stream) => stream, - ElectrumStream::Tls(stream) => stream.get_ref().0, - } - } -} - -#[cfg(not(target_arch = "wasm32"))] -impl AsyncRead for ElectrumStream { - fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { - match self.get_mut() { - ElectrumStream::Tcp(stream) => AsyncRead::poll_read(Pin::new(stream), cx, buf), - ElectrumStream::Tls(stream) => AsyncRead::poll_read(Pin::new(stream), cx, buf), - } - } -} - -#[cfg(not(target_arch = "wasm32"))] -impl AsyncWrite for ElectrumStream { - fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { - match self.get_mut() { - ElectrumStream::Tcp(stream) => AsyncWrite::poll_write(Pin::new(stream), cx, buf), - ElectrumStream::Tls(stream) => AsyncWrite::poll_write(Pin::new(stream), cx, buf), - } - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.get_mut() { - ElectrumStream::Tcp(stream) => AsyncWrite::poll_flush(Pin::new(stream), cx), - ElectrumStream::Tls(stream) => AsyncWrite::poll_flush(Pin::new(stream), cx), - } - } - - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.get_mut() { - ElectrumStream::Tcp(stream) => AsyncWrite::poll_shutdown(Pin::new(stream), cx), - ElectrumStream::Tls(stream) => AsyncWrite::poll_shutdown(Pin::new(stream), cx), - } - } -} - -const ELECTRUM_TIMEOUT: u64 = 60; - -async fn electrum_last_chunk_loop(last_chunk: Arc) { - loop { - Timer::sleep(ELECTRUM_TIMEOUT as f64).await; - let last = (last_chunk.load(AtomicOrdering::Relaxed) / 1000) as f64; - if now_float() - last > ELECTRUM_TIMEOUT as f64 { - warn!( - "Didn't receive any data since {}. Shutting down the connection.", - last as i64 - ); - break; - } - } -} - -#[cfg(not(target_arch = "wasm32"))] -fn rustls_client_config(unsafe_conf: bool) -> Arc { - let mut cert_store = RootCertStore::empty(); - - cert_store.add_trust_anchors( - TLS_SERVER_ROOTS - .iter() - .map(|ta| OwnedTrustAnchor::from_subject_spki_name_constraints(ta.subject, ta.spki, ta.name_constraints)), - ); - - let mut tls_config = rustls::ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(cert_store) - .with_no_client_auth(); - - if unsafe_conf { - tls_config - .dangerous() - .set_certificate_verifier(Arc::new(NoCertificateVerification {})); - } - Arc::new(tls_config) -} - -#[cfg(not(target_arch = "wasm32"))] -lazy_static! { - static ref SAFE_TLS_CONFIG: Arc = rustls_client_config(false); - static ref UNSAFE_TLS_CONFIG: Arc = rustls_client_config(true); -} - -#[cfg(not(target_arch = "wasm32"))] -async fn connect_loop( - config: ElectrumConfig, - addr: String, - responses: JsonRpcPendingRequestsShared, - connection_tx: Arc>>>>, - event_handlers: Vec, - scripthash_notification_sender: ScripthashNotificationSender, - _spawner: Spawner, -) -> Result<(), ()> { - let delay = Arc::new(AtomicU64::new(0)); - - loop { - let current_delay = delay.load(AtomicOrdering::Relaxed); - if current_delay > 0 { - Timer::sleep(current_delay as f64).await; - }; - - let socket_addr = addr_to_socket_addr(&addr).map_err(|e| { - error!("{:?} error {:?}", addr, e); - })?; - - let connect_f = match config.clone() { - ElectrumConfig::TCP => Either::Left(TcpStream::connect(&socket_addr).map_ok(ElectrumStream::Tcp)), - ElectrumConfig::SSL { - dns_name, - skip_validation, - } => { - let tls_connector = if skip_validation { - TlsConnector::from(UNSAFE_TLS_CONFIG.clone()) - } else { - TlsConnector::from(SAFE_TLS_CONFIG.clone()) - }; - // The address should always be correct since we checked it beforehand in initializaiton. - let dns = server_name_from_domain(dns_name.as_str()).map_err(|e| { - error!("{:?} error {:?}", addr, e); - })?; - - Either::Right( - TcpStream::connect(&socket_addr) - .and_then(move |stream| tls_connector.connect(dns, stream).map_ok(ElectrumStream::Tls)), - ) - }, - }; - - let stream = try_loop!(connect_f.await, addr, delay); - try_loop!(stream.as_ref().set_nodelay(true), addr, delay); - info!("Electrum client connected to {}", addr); - try_loop!(event_handlers.on_connected(addr.clone()), addr, delay); - let last_chunk = Arc::new(AtomicU64::new(now_ms())); - let mut last_chunk_f = electrum_last_chunk_loop(last_chunk.clone()).boxed().fuse(); - - let (tx, rx) = mpsc::channel(0); - *connection_tx.lock().await = Some(tx); - let rx = rx_to_stream(rx).inspect(|data| { - // measure the length of each sent packet - event_handlers.on_outgoing_request(data); - }); - - let (read, mut write) = tokio::io::split(stream); - let recv_f = { - let delay = delay.clone(); - let addr = addr.clone(); - let responses = responses.clone(); - let scripthash_notification_sender = scripthash_notification_sender.clone(); - let event_handlers = event_handlers.clone(); - async move { - let mut buffer = String::with_capacity(1024); - let mut buf_reader = BufReader::new(read); - loop { - match buf_reader.read_line(&mut buffer).await { - Ok(c) => { - if c == 0 { - info!("EOF from {}", addr); - break; - } - // reset the delay if we've connected successfully and only if we received some data from connection - delay.store(0, AtomicOrdering::Relaxed); - }, - Err(e) => { - error!("Error on read {} from {}", e, addr); - break; - }, - }; - // measure the length of each incoming packet - event_handlers.on_incoming_response(buffer.as_bytes()); - last_chunk.store(now_ms(), AtomicOrdering::Relaxed); - - electrum_process_chunk(buffer.as_bytes(), &responses, scripthash_notification_sender.clone()).await; - buffer.clear(); - } - } - }; - let mut recv_f = Box::pin(recv_f).fuse(); - - let send_f = { - let addr = addr.clone(); - let mut rx = rx.compat(); - async move { - while let Some(Ok(bytes)) = rx.next().await { - if let Err(e) = write.write_all(&bytes).await { - error!("Write error {} to {}", e, addr); - } - } - } - }; - let mut send_f = Box::pin(send_f).fuse(); - macro_rules! reset_tx_and_continue { - () => { - info!("{} connection dropped", addr); - event_handlers.on_disconnected(addr.clone()).error_log(); - *connection_tx.lock().await = None; - increase_delay(&delay); - continue; - }; - } - - select! { - _last_chunk = last_chunk_f => { reset_tx_and_continue!(); }, - _recv = recv_f => { reset_tx_and_continue!(); }, - _send = send_f => { reset_tx_and_continue!(); }, - } - } -} - -#[cfg(target_arch = "wasm32")] -async fn connect_loop( - _config: ElectrumConfig, - addr: String, - responses: JsonRpcPendingRequestsShared, - connection_tx: Arc>>>>, - event_handlers: Vec, - scripthash_notification_sender: ScripthashNotificationSender, - spawner: Spawner, -) -> Result<(), ()> { - use std::sync::atomic::AtomicUsize; - - lazy_static! { - static ref CONN_IDX: Arc = Arc::new(AtomicUsize::new(0)); - } - - use mm2_net::wasm::wasm_ws::ws_transport; - - let delay = Arc::new(AtomicU64::new(0)); - loop { - let current_delay = delay.load(AtomicOrdering::Relaxed); - if current_delay > 0 { - Timer::sleep(current_delay as f64).await; - } - - let conn_idx = CONN_IDX.fetch_add(1, AtomicOrdering::Relaxed); - let (mut transport_tx, mut transport_rx) = - try_loop!(ws_transport(conn_idx, &addr, &spawner).await, addr, delay); - - info!("Electrum client connected to {}", addr); - try_loop!(event_handlers.on_connected(addr.clone()), addr, delay); - - let last_chunk = Arc::new(AtomicU64::new(now_ms())); - let mut last_chunk_fut = electrum_last_chunk_loop(last_chunk.clone()).boxed().fuse(); - - let (outgoing_tx, outgoing_rx) = mpsc::channel(0); - *connection_tx.lock().await = Some(outgoing_tx); - - let incoming_fut = { - let delay = delay.clone(); - let addr = addr.clone(); - let responses = responses.clone(); - let scripthash_notification_sender = scripthash_notification_sender.clone(); - let event_handlers = event_handlers.clone(); - async move { - while let Some(incoming_res) = transport_rx.next().await { - last_chunk.store(now_ms(), AtomicOrdering::Relaxed); - match incoming_res { - Ok(incoming_json) => { - // reset the delay if we've connected successfully and only if we received some data from connection - delay.store(0, AtomicOrdering::Relaxed); - // measure the length of each incoming packet - let incoming_str = incoming_json.to_string(); - event_handlers.on_incoming_response(incoming_str.as_bytes()); - - electrum_process_json(incoming_json, &responses, &scripthash_notification_sender).await; - }, - Err(e) => { - error!("{} error: {:?}", addr, e); - }, - } - } - } - }; - let mut incoming_fut = Box::pin(incoming_fut).fuse(); - - let outgoing_fut = { - let addr = addr.clone(); - let mut outgoing_rx = rx_to_stream(outgoing_rx).compat(); - let event_handlers = event_handlers.clone(); - async move { - while let Some(Ok(data)) = outgoing_rx.next().await { - let raw_json: Json = match json::from_slice(&data) { - Ok(js) => js, - Err(e) => { - error!("Error {} deserializing the outgoing data: {:?}", e, data); - continue; - }, - }; - // measure the length of each sent packet - event_handlers.on_outgoing_request(&data); - - if let Err(e) = transport_tx.send(raw_json).await { - error!("Error sending to {}: {:?}", addr, e); - } - } - } - }; - let mut outgoing_fut = Box::pin(outgoing_fut).fuse(); - - macro_rules! reset_tx_and_continue { - () => { - info!("{} connection dropped", addr); - *connection_tx.lock().await = None; - event_handlers.on_disconnected(addr.clone()).error_log(); - increase_delay(&delay); - continue; - }; - } - - select! { - _last_chunk = last_chunk_fut => { reset_tx_and_continue!(); }, - _incoming = incoming_fut => { reset_tx_and_continue!(); }, - _outgoing = outgoing_fut => { reset_tx_and_continue!(); }, - } - } -} - -/// Builds up the electrum connection, spawns endless loop that attempts to reconnect to the server -/// in case of connection errors. -/// The function takes `abortable_system` that will be used to spawn Electrum's related futures. -fn electrum_connect( - addr: String, - config: ElectrumConfig, - event_handlers: Vec, - scripthash_notification_sender: &ScripthashNotificationSender, - abortable_system: AbortableQueue, -) -> ElectrumConnection { - let responses = Arc::new(AsyncMutex::new(JsonRpcPendingRequests::default())); - let tx = Arc::new(AsyncMutex::new(None)); - - let spawner = abortable_system.weak_spawner(); - let fut = connect_loop( - config.clone(), - addr.clone(), - responses.clone(), - tx.clone(), - event_handlers, - scripthash_notification_sender.clone(), - spawner.clone(), - ) - .then(|_| futures::future::ready(())); - - spawner.spawn(fut); - ElectrumConnection { - addr, - config, - tx, - responses, - protocol_version: AsyncMutex::new(None), - _abortable_system: abortable_system, - } -} - -/// # Important -/// `electrum_request` should always return [`JsonRpcErrorType::Transport`] error. -fn electrum_request( - mut req_json: String, - rpc_id: JsonRpcId, - tx: mpsc::Sender>, - responses: JsonRpcPendingRequestsShared, - timeout: u64, -) -> Box + Send + 'static> { - let send_fut = async move { - #[cfg(not(target_arch = "wasm"))] - { - // Electrum request and responses must end with \n - // https://electrumx.readthedocs.io/en/latest/protocol-basics.html#message-stream - req_json.push('\n'); - } - let (req_tx, resp_rx) = async_oneshot::channel(); - responses.lock().await.insert(rpc_id, req_tx); - tx.send(req_json.into_bytes()) - .compat() - .await - .map_err(|err| JsonRpcErrorType::Transport(err.to_string()))?; - let resps = resp_rx.await.map_err(|e| JsonRpcErrorType::Transport(e.to_string()))?; - Ok(resps) - }; - let send_fut = send_fut - .boxed() - .timeout(Duration::from_secs(timeout)) - .compat() - .then(move |res| res.map_err(|err| JsonRpcErrorType::Transport(err.to_string()))?); - Box::new(send_fut) -} - fn address_balance_from_unspent_map(address: &Address, unspent_map: &UnspentMap, decimals: u8) -> BigDecimal { let unspents = match unspent_map.get(address) { Some(unspents) => unspents, diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/client.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/client.rs new file mode 100644 index 0000000000..ce0498cc31 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/client.rs @@ -0,0 +1,1068 @@ +use super::super::{BlockHashOrHeight, EstimateFeeMethod, EstimateFeeMode, SpentOutputInfo, UnspentInfo, UnspentMap, + UtxoJsonRpcClientInfo, UtxoRpcClientOps, UtxoRpcError, UtxoRpcFut}; +use super::connection::{ElectrumConnection, ElectrumConnectionErr, ElectrumConnectionSettings}; +use super::connection_manager::ConnectionManager; +use super::constants::{BLOCKCHAIN_HEADERS_SUB_ID, BLOCKCHAIN_SCRIPTHASH_SUB_ID, ELECTRUM_REQUEST_TIMEOUT, + NO_FORCE_CONNECT_METHODS, SEND_TO_ALL_METHODS}; +use super::electrum_script_hash; +use super::event_handlers::{ElectrumConnectionManagerNotifier, ElectrumScriptHashNotificationBridge}; +use super::rpc_responses::*; + +use crate::utxo::rpc_clients::ConcurrentRequestMap; +use crate::utxo::utxo_block_header_storage::BlockHeaderStorage; +use crate::utxo::{output_script, output_script_p2pk, GetBlockHeaderError, GetConfirmedTxError, GetTxHeightError, + ScripthashNotification}; +use crate::RpcTransportEventHandler; +use crate::SharableRpcTransportEventHandler; +use chain::{BlockHeader, Transaction as UtxoTx, TxHashAlgo}; +use common::executor::abortable_queue::{AbortableQueue, WeakSpawner}; +use common::jsonrpc_client::{JsonRpcBatchClient, JsonRpcClient, JsonRpcError, JsonRpcErrorType, JsonRpcId, + JsonRpcMultiClient, JsonRpcRemoteAddr, JsonRpcRequest, JsonRpcRequestEnum, + JsonRpcResponseEnum, JsonRpcResponseFut, RpcRes}; +use common::log::warn; +use common::{median, OrdRange}; +use keys::hash::H256; +use keys::Address; +use mm2_err_handle::prelude::*; +use mm2_number::BigDecimal; +#[cfg(test)] use mocktopus::macros::*; +use rpc::v1::types::{Bytes as BytesJson, Transaction as RpcTransaction, H256 as H256Json}; +use serialization::{deserialize, serialize, serialize_with_flags, CoinVariant, CompactInteger, Reader, + SERIALIZE_TRANSACTION_WITNESS}; +use spv_validation::helpers_validation::SPVError; +use spv_validation::storage::BlockHeaderStorageOps; + +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::collections::HashSet; +use std::convert::TryInto; +use std::fmt::Debug; +use std::iter::FromIterator; +use std::num::NonZeroU64; +use std::ops::Deref; +use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; +use std::sync::Arc; + +use async_trait::async_trait; +use futures::channel::mpsc::UnboundedSender; +use futures::compat::Future01CompatExt; +use futures::future::{join_all, FutureExt, TryFutureExt}; +use futures::stream::FuturesUnordered; +use futures::StreamExt; +use futures01::Future; +use itertools::Itertools; +use serde_json::{self as json, Value as Json}; + +type ElectrumTxHistory = Vec; +type ElectrumScriptHash = String; +type ScriptHashUnspents = Vec; + +#[derive(Debug)] +pub struct ElectrumClientSettings { + pub client_name: String, + pub servers: Vec, + pub coin_ticker: String, + pub negotiate_version: bool, + pub spawn_ping: bool, + /// Minimum number of connections to keep alive at all times (best effort). + pub min_connected: usize, + /// Maximum number of connections to keep alive at any time. + pub max_connected: usize, +} + +#[derive(Debug)] +pub struct ElectrumClientImpl { + client_name: String, + coin_ticker: String, + pub connection_manager: ConnectionManager, + next_id: AtomicU64, + negotiate_version: bool, + protocol_version: OrdRange, + get_balance_concurrent_map: ConcurrentRequestMap, + list_unspent_concurrent_map: ConcurrentRequestMap>, + block_headers_storage: BlockHeaderStorage, + /// Event handlers that are triggered on (dis)connection & transport events. They are wrapped + /// in an `Arc` since they are shared outside `ElectrumClientImpl`. They are handed to each active + /// `ElectrumConnection` to notify them about the events. + event_handlers: Arc>>, + pub scripthash_notification_sender: Option>, + abortable_system: AbortableQueue, +} + +#[cfg_attr(test, mockable)] +impl ElectrumClientImpl { + /// Returns a new instance of `ElectrumClientImpl`. + /// + /// This doesn't initialize the connection manager contained within `ElectrumClientImpl`. + /// Use `try_new_arc` to create an Arc-wrapped instance with an initialized connection manager. + fn try_new( + client_settings: ElectrumClientSettings, + block_headers_storage: BlockHeaderStorage, + abortable_system: AbortableQueue, + mut event_handlers: Vec>, + scripthash_notification_sender: Option>, + ) -> Result { + // This is used for balance event streaming implementation for UTXOs. + // Will be used for sending scripthash messages to trigger re-connections, re-fetching the balances, etc. + if let Some(scripthash_notification_sender) = scripthash_notification_sender.clone() { + event_handlers.push(Box::new(ElectrumScriptHashNotificationBridge { + scripthash_notification_sender, + })); + } + + let connection_manager = ConnectionManager::try_new( + client_settings.servers, + client_settings.spawn_ping, + (client_settings.min_connected, client_settings.max_connected), + &abortable_system, + )?; + + event_handlers.push(Box::new(ElectrumConnectionManagerNotifier { + connection_manager: connection_manager.clone(), + })); + + Ok(ElectrumClientImpl { + client_name: client_settings.client_name, + coin_ticker: client_settings.coin_ticker, + connection_manager, + next_id: 0.into(), + negotiate_version: client_settings.negotiate_version, + protocol_version: OrdRange::new(1.2, 1.4).unwrap(), + get_balance_concurrent_map: ConcurrentRequestMap::new(), + list_unspent_concurrent_map: ConcurrentRequestMap::new(), + block_headers_storage, + abortable_system, + scripthash_notification_sender, + event_handlers: Arc::new(event_handlers), + }) + } + + /// Create a new Electrum client instance. + /// This function initializes the connection manager and starts the connection process. + pub fn try_new_arc( + client_settings: ElectrumClientSettings, + block_headers_storage: BlockHeaderStorage, + abortable_system: AbortableQueue, + event_handlers: Vec>, + scripthash_notification_sender: Option>, + ) -> Result, String> { + let client_impl = Arc::new(ElectrumClientImpl::try_new( + client_settings, + block_headers_storage, + abortable_system, + event_handlers, + scripthash_notification_sender, + )?); + // Initialize the connection manager. + client_impl + .connection_manager + .initialize(Arc::downgrade(&client_impl)) + .map_err(|e| e.to_string())?; + + Ok(client_impl) + } + + /// Remove an Electrum connection and stop corresponding spawned actor. + pub fn remove_server(&self, server_addr: &str) -> Result, String> { + self.connection_manager + .remove_connection(server_addr) + .map_err(|err| err.to_string()) + } + + /// Check if all connections have been removed. + pub fn is_connections_pool_empty(&self) -> bool { self.connection_manager.is_connections_pool_empty() } + + /// Get available protocol versions. + pub fn protocol_version(&self) -> &OrdRange { &self.protocol_version } + + pub fn coin_ticker(&self) -> &str { &self.coin_ticker } + + /// Whether to negotiate the protocol version. + pub fn negotiate_version(&self) -> bool { self.negotiate_version } + + /// Get the event handlers. + pub fn event_handlers(&self) -> Arc>> { self.event_handlers.clone() } + + /// Sends a list of addresses through the scripthash notification sender to subscribe to their scripthash notifications. + pub fn subscribe_addresses(&self, addresses: HashSet
) -> Result<(), String> { + if let Some(sender) = &self.scripthash_notification_sender { + sender + .unbounded_send(ScripthashNotification::SubscribeToAddresses(addresses)) + .map_err(|e| ERRL!("Failed sending scripthash message. {}", e))?; + } + + Ok(()) + } + + /// Get block headers storage. + pub fn block_headers_storage(&self) -> &BlockHeaderStorage { &self.block_headers_storage } + + pub fn weak_spawner(&self) -> WeakSpawner { self.abortable_system.weak_spawner() } + + #[cfg(test)] + pub fn with_protocol_version( + client_settings: ElectrumClientSettings, + block_headers_storage: BlockHeaderStorage, + abortable_system: AbortableQueue, + event_handlers: Vec>, + scripthash_notification_sender: Option>, + protocol_version: OrdRange, + ) -> Result, String> { + let client_impl = Arc::new(ElectrumClientImpl { + protocol_version, + ..ElectrumClientImpl::try_new( + client_settings, + block_headers_storage, + abortable_system, + event_handlers, + scripthash_notification_sender, + )? + }); + // Initialize the connection manager. + client_impl + .connection_manager + .initialize(Arc::downgrade(&client_impl)) + .map_err(|e| e.to_string())?; + + Ok(client_impl) + } +} + +#[derive(Clone, Debug)] +pub struct ElectrumClient(pub Arc); + +impl Deref for ElectrumClient { + type Target = ElectrumClientImpl; + fn deref(&self) -> &ElectrumClientImpl { &self.0 } +} + +impl UtxoJsonRpcClientInfo for ElectrumClient { + fn coin_name(&self) -> &str { self.coin_ticker.as_str() } +} + +impl JsonRpcClient for ElectrumClient { + fn version(&self) -> &'static str { "2.0" } + + fn next_id(&self) -> u64 { self.next_id.fetch_add(1, AtomicOrdering::Relaxed) } + + fn client_info(&self) -> String { UtxoJsonRpcClientInfo::client_info(self) } + + fn transport(&self, request: JsonRpcRequestEnum) -> JsonRpcResponseFut { + Box::new(self.clone().electrum_request_multi(request).boxed().compat()) + } +} + +impl JsonRpcBatchClient for ElectrumClient {} + +impl JsonRpcMultiClient for ElectrumClient { + fn transport_exact(&self, to_addr: String, request: JsonRpcRequestEnum) -> JsonRpcResponseFut { + Box::new( + self.clone() + .electrum_request_to(to_addr.clone(), request) + .map_ok(|response| (JsonRpcRemoteAddr(to_addr), response)) + .boxed() + .compat(), + ) + } +} + +#[cfg_attr(test, mockable)] +impl ElectrumClient { + pub fn try_new( + client_settings: ElectrumClientSettings, + event_handlers: Vec>, + block_headers_storage: BlockHeaderStorage, + abortable_system: AbortableQueue, + scripthash_notification_sender: Option>, + ) -> Result { + let client = ElectrumClient(ElectrumClientImpl::try_new_arc( + client_settings, + block_headers_storage, + abortable_system, + event_handlers, + scripthash_notification_sender, + )?); + + Ok(client) + } + + /// Sends a JSONRPC request to all the connected servers. + /// + /// This method will block until a response is received from at least one server. + async fn electrum_request_multi( + self, + request: JsonRpcRequestEnum, + ) -> Result<(JsonRpcRemoteAddr, JsonRpcResponseEnum), JsonRpcErrorType> { + // Whether to send the request to all active connections or not. + let send_to_all = matches!(request, JsonRpcRequestEnum::Single(ref req) if SEND_TO_ALL_METHODS.contains(&req.method.as_str())); + // Request id and serialized request. + let req_id = request.rpc_id(); + let request = json::to_string(&request).map_err(|e| JsonRpcErrorType::InvalidRequest(e.to_string()))?; + let request = (req_id, request); + // Use the active connections for this request. + let connections = self.connection_manager.get_active_connections(); + // Maximum number of connections to establish or use in request concurrently. Could be up to connections.len(). + let concurrency = if send_to_all { connections.len() } else { 1 }; + match self + .send_request_using(&request, connections, send_to_all, concurrency) + .await + { + Ok(response) => Ok(response), + // If we failed the request using only the active connections, try again using all connections. + Err(_) if !send_to_all => { + warn!( + "[coin={}] Failed to send the request using active connections, trying all connections.", + self.coin_ticker() + ); + let connections = self.connection_manager.get_all_connections(); + // At this point we should have all the connections disconnected since all + // the active connections failed (and we disconnected them in the process). + // So use a higher concurrency to speed up the response time. + // + // Note that a side effect of this is that we might break the `max_connected` threshold for + // a short time since the connection manager's background task will be trying to establish + // connections at the same time. This is not as bad though since the manager's background task + // tries connections sequentially and we are expected for finish much quicker due to parallelizing. + let concurrency = self.connection_manager.config().max_connected; + match self.send_request_using(&request, connections, false, concurrency).await { + Ok(response) => Ok(response), + Err(err_vec) => Err(JsonRpcErrorType::Internal(format!("All servers errored: {err_vec:?}"))), + } + }, + Err(e) => Err(JsonRpcErrorType::Internal(format!("All servers errored: {e:?}"))), + } + } + + /// Sends a JSONRPC request to a specific electrum server. + /// + /// This will try to wake up the server connection if it's not connected. + async fn electrum_request_to( + self, + to_addr: String, + request: JsonRpcRequestEnum, + ) -> Result { + // Whether to force the connection to be established (if not) before sending the request. + let force_connect = !matches!(request, JsonRpcRequestEnum::Single(ref req) if NO_FORCE_CONNECT_METHODS.contains(&req.method.as_str())); + let json = json::to_string(&request).map_err(|err| JsonRpcErrorType::InvalidRequest(err.to_string()))?; + + let connection = self + .connection_manager + .get_connection_by_address(&to_addr, force_connect) + .await + .map_err(|err| JsonRpcErrorType::Internal(err.to_string()))?; + + let response = connection + .electrum_request(json, request.rpc_id(), ELECTRUM_REQUEST_TIMEOUT) + .await; + // If the request was not forcefully connected, we shouldn't inform the connection manager that it's + // not needed anymore, as we didn't force spawn it in the first place. + // This fixes dropping the connection after the version check request, as we don't mark the connection + // maintained till after the version is checked. + if force_connect { + // Inform the connection manager that the connection was queried and no longer needed now. + self.connection_manager.not_needed(&to_addr); + } + + response + } + + /// Sends a JSONRPC request to all the given connections in parallel and returns + /// the first successful response if there is any, or a vector of errors otherwise. + /// + /// If `send_to_all` is set to `true`, we won't return on first successful response but + /// wait for all responses to come back first. + async fn send_request_using( + &self, + request: &(JsonRpcId, String), + connections: Vec>, + send_to_all: bool, + max_concurrency: usize, + ) -> Result<(JsonRpcRemoteAddr, JsonRpcResponseEnum), Vec<(JsonRpcRemoteAddr, JsonRpcErrorType)>> { + let max_concurrency = max_concurrency.max(1); + // Create the request + let chunked_requests = connections.chunks(max_concurrency).map(|chunk| { + FuturesUnordered::from_iter(chunk.iter().map(|connection| { + let client = self.clone(); + let req_id = request.0; + let req_json = request.1.clone(); + async move { + let connection_is_established = connection + // We first make sure that the connection loop is established before sending the request. + .establish_connection_loop(client) + .await + .map_err(|e| JsonRpcErrorType::Transport(format!("Failed to establish connection: {e:?}"))); + let response = match connection_is_established { + Ok(_) => { + // Perform the request. + connection + .electrum_request(req_json, req_id, ELECTRUM_REQUEST_TIMEOUT) + .await + }, + Err(e) => Err(e), + }; + (response, connection.clone()) + } + })) + }); + let client = self.clone(); + let mut final_response = None; + let mut errors = Vec::new(); + // Iterate over the request chunks sequentially. + for mut requests in chunked_requests { + // For each chunk, iterate over the requests in parallel. + while let Some((response, connection)) = requests.next().await { + let address = JsonRpcRemoteAddr(connection.address().to_string()); + match response { + Ok(response) => { + if final_response.is_none() { + final_response = Some((address, response)); + } + client.connection_manager.not_needed(connection.address()); + if !send_to_all && final_response.is_some() { + return Ok(final_response.unwrap()); + } + }, + Err(e) => { + warn!( + "[coin={}], Error while sending request to {address:?}: {e:?}", + client.coin_ticker() + ); + connection.disconnect(Some(ElectrumConnectionErr::Temporary(format!( + "Forcefully disconnected for erroring: {e:?}." + )))); + client.event_handlers.on_disconnected(connection.address()).ok(); + errors.push((address, e)) + }, + } + } + } + final_response.ok_or(errors) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#server-ping + pub fn server_ping(&self) -> RpcRes<()> { rpc_func!(self, "server.ping") } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#server-version + pub fn server_version(&self, server_address: &str, version: &OrdRange) -> RpcRes { + let protocol_version: Vec = version.flatten().into_iter().map(|v| format!("{}", v)).collect(); + rpc_func_from!( + self, + server_address, + "server.version", + &self.client_name, + protocol_version + ) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe + pub fn get_block_count_from(&self, server_address: &str) -> RpcRes { + Box::new( + rpc_func_from!(self, server_address, BLOCKCHAIN_HEADERS_SUB_ID) + .map(|r: ElectrumBlockHeader| r.block_height()), + ) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-headers + pub fn get_block_headers_from( + &self, + server_address: &str, + start_height: u64, + count: NonZeroU64, + ) -> RpcRes { + rpc_func_from!(self, server_address, "blockchain.block.headers", start_height, count) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-listunspent + /// It can return duplicates sometimes: https://github.com/artemii235/SuperNET/issues/269 + /// We should remove them to build valid transactions + pub fn scripthash_list_unspent(&self, hash: &str) -> RpcRes> { + let request_fut = Box::new(rpc_func!(self, "blockchain.scripthash.listunspent", hash).and_then( + move |unspents: Vec| { + let mut map: HashMap<(H256Json, u32), bool> = HashMap::new(); + let unspents = unspents + .into_iter() + .filter(|unspent| match map.entry((unspent.tx_hash, unspent.tx_pos)) { + Entry::Occupied(_) => false, + Entry::Vacant(e) => { + e.insert(true); + true + }, + }) + .collect(); + Ok(unspents) + }, + )); + let arc = self.clone(); + let hash = hash.to_owned(); + let fut = async move { arc.list_unspent_concurrent_map.wrap_request(hash, request_fut).await }; + Box::new(fut.boxed().compat()) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-listunspent + /// It can return duplicates sometimes: https://github.com/artemii235/SuperNET/issues/269 + /// We should remove them to build valid transactions. + /// Please note the function returns `ScriptHashUnspents` elements in the same order in which they were requested. + pub fn scripthash_list_unspent_batch(&self, hashes: Vec) -> RpcRes> { + let requests = hashes + .iter() + .map(|hash| rpc_req!(self, "blockchain.scripthash.listunspent", hash)); + Box::new(self.batch_rpc(requests).map(move |unspents: Vec| { + unspents + .into_iter() + .map(|hash_unspents| { + hash_unspents + .into_iter() + .unique_by(|unspent| (unspent.tx_hash, unspent.tx_pos)) + .collect::>() + }) + .collect() + })) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-get-history + pub fn scripthash_get_history(&self, hash: &str) -> RpcRes { + rpc_func!(self, "blockchain.scripthash.get_history", hash) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-get-history + /// Requests history of the `hashes` in a batch and returns them in the same order they were requested. + pub fn scripthash_get_history_batch(&self, hashes: I) -> RpcRes> + where + I: IntoIterator, + { + let requests = hashes + .into_iter() + .map(|hash| rpc_req!(self, "blockchain.scripthash.get_history", hash)); + self.batch_rpc(requests) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-gethistory + pub fn scripthash_get_balance(&self, hash: &str) -> RpcRes { + let arc = self.clone(); + let hash = hash.to_owned(); + let fut = async move { + let request = rpc_func!(arc, "blockchain.scripthash.get_balance", &hash); + arc.get_balance_concurrent_map.wrap_request(hash, request).await + }; + Box::new(fut.boxed().compat()) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-gethistory + /// Requests balances in a batch and returns them in the same order they were requested. + pub fn scripthash_get_balances(&self, hashes: I) -> RpcRes> + where + I: IntoIterator, + { + let requests = hashes + .into_iter() + .map(|hash| rpc_req!(self, "blockchain.scripthash.get_balance", &hash)); + self.batch_rpc(requests) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe + pub fn blockchain_headers_subscribe(&self) -> RpcRes { + rpc_func!(self, BLOCKCHAIN_HEADERS_SUB_ID) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-broadcast + pub fn blockchain_transaction_broadcast(&self, tx: BytesJson) -> RpcRes { + rpc_func!(self, "blockchain.transaction.broadcast", tx) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-estimatefee + /// It is recommended to set n_blocks as low as possible. + /// However, in some cases, n_blocks = 1 leads to an unreasonably high fee estimation. + /// https://github.com/KomodoPlatform/atomicDEX-API/issues/656#issuecomment-743759659 + pub fn estimate_fee(&self, mode: &Option, n_blocks: u32) -> UtxoRpcFut { + match mode { + Some(m) => { + Box::new(rpc_func!(self, "blockchain.estimatefee", n_blocks, m).map_to_mm_fut(UtxoRpcError::from)) + }, + None => Box::new(rpc_func!(self, "blockchain.estimatefee", n_blocks).map_to_mm_fut(UtxoRpcError::from)), + } + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-header + pub fn blockchain_block_header(&self, height: u64) -> RpcRes { + rpc_func!(self, "blockchain.block.header", height) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-headers + pub fn blockchain_block_headers(&self, start_height: u64, count: NonZeroU64) -> RpcRes { + rpc_func!(self, "blockchain.block.headers", start_height, count) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get-merkle + pub fn blockchain_transaction_get_merkle(&self, txid: H256Json, height: u64) -> RpcRes { + rpc_func!(self, "blockchain.transaction.get_merkle", txid, height) + } + + // get_tx_height_from_rpc is costly since it loops through history after requesting the whole history of the script pubkey + // This method should always be used if the block headers are saved to the DB + async fn get_tx_height_from_storage(&self, tx: &UtxoTx) -> Result> { + let tx_hash = tx.hash().reversed(); + let blockhash = self.get_verbose_transaction(&tx_hash.into()).compat().await?.blockhash; + Ok(self + .block_headers_storage() + .get_block_height_by_hash(blockhash.into()) + .await? + .ok_or_else(|| { + GetTxHeightError::HeightNotFound(format!( + "Transaction block header is not found in storage for {}", + self.coin_ticker() + )) + })? + .try_into()?) + } + + // get_tx_height_from_storage is always preferred to be used instead of this, but if there is no headers in storage (storing headers is not enabled) + // this function can be used instead + async fn get_tx_height_from_rpc(&self, tx: &UtxoTx) -> Result { + let selfi = self; + for output in tx.outputs.clone() { + let script_pubkey_str = hex::encode(electrum_script_hash(&output.script_pubkey)); + if let Ok(history) = selfi.scripthash_get_history(script_pubkey_str.as_str()).compat().await { + if let Some(item) = history + .into_iter() + .find(|item| item.tx_hash.reversed() == H256Json(*tx.hash()) && item.height > 0) + { + return Ok(item.height as u64); + } + } + } + Err(GetTxHeightError::HeightNotFound(format!( + "Couldn't find height through electrum for {}", + selfi.coin_ticker + ))) + } + + async fn block_header_from_storage(&self, height: u64) -> Result> { + self.block_headers_storage() + .get_block_header(height) + .await? + .ok_or_else(|| { + GetBlockHeaderError::Internal(format!("Header not found in storage for {}", self.coin_ticker)).into() + }) + } + + async fn block_header_from_storage_or_rpc(&self, height: u64) -> Result> { + match self.block_header_from_storage(height).await { + Ok(h) => Ok(h), + Err(_) => Ok(deserialize( + self.blockchain_block_header(height).compat().await?.as_slice(), + )?), + } + } + + pub async fn get_confirmed_tx_info_from_rpc( + &self, + tx: &UtxoTx, + ) -> Result { + let height = self.get_tx_height_from_rpc(tx).await?; + + let merkle_branch = self + .blockchain_transaction_get_merkle(tx.hash().reversed().into(), height) + .compat() + .await?; + + let header = deserialize(self.blockchain_block_header(height).compat().await?.as_slice())?; + + Ok(ConfirmedTransactionInfo { + tx: tx.clone(), + header, + index: merkle_branch.pos as u64, + height, + }) + } + + pub async fn get_merkle_and_validated_header( + &self, + tx: &UtxoTx, + ) -> Result<(TxMerkleBranch, BlockHeader, u64), MmError> { + let height = self.get_tx_height_from_storage(tx).await?; + + let merkle_branch = self + .blockchain_transaction_get_merkle(tx.hash().reversed().into(), height) + .compat() + .await + .map_to_mm(|err| SPVError::UnableToGetMerkle { + coin: self.coin_ticker.clone(), + err: err.to_string(), + })?; + + let header = self.block_header_from_storage(height).await?; + + Ok((merkle_branch, header, height)) + } + + pub fn retrieve_headers_from( + &self, + server_address: &str, + from_height: u64, + to_height: u64, + ) -> UtxoRpcFut<(HashMap, Vec)> { + let coin_name = self.coin_ticker.clone(); + if from_height == 0 || to_height < from_height { + return Box::new(futures01::future::err( + UtxoRpcError::Internal("Invalid values for from/to parameters".to_string()).into(), + )); + } + let count: NonZeroU64 = match (to_height - from_height + 1).try_into() { + Ok(c) => c, + Err(e) => return Box::new(futures01::future::err(UtxoRpcError::Internal(e.to_string()).into())), + }; + Box::new( + self.get_block_headers_from(server_address, from_height, count) + .map_to_mm_fut(UtxoRpcError::from) + .and_then(move |headers| { + let (block_registry, block_headers) = { + if headers.count == 0 { + return MmError::err(UtxoRpcError::Internal("No headers available".to_string())); + } + let len = CompactInteger::from(headers.count); + let mut serialized = serialize(&len).take(); + serialized.extend(headers.hex.0.into_iter()); + drop_mutability!(serialized); + let mut reader = + Reader::new_with_coin_variant(serialized.as_slice(), coin_name.as_str().into()); + let maybe_block_headers = reader.read_list::(); + let block_headers = match maybe_block_headers { + Ok(headers) => headers, + Err(e) => return MmError::err(UtxoRpcError::InvalidResponse(format!("{:?}", e))), + }; + let mut block_registry: HashMap = HashMap::new(); + let mut starting_height = from_height; + for block_header in &block_headers { + block_registry.insert(starting_height, block_header.clone()); + starting_height += 1; + } + (block_registry, block_headers) + }; + Ok((block_registry, block_headers)) + }), + ) + } + + pub(crate) fn get_servers_with_latest_block_count(&self) -> UtxoRpcFut<(Vec, u64)> { + let selfi = self.clone(); + let fut = async move { + let addresses = selfi.connection_manager.get_all_server_addresses(); + let futures = addresses + .into_iter() + .map(|address| { + selfi + .get_block_count_from(&address) + .map(|response| (address, response)) + .compat() + }) + .collect::>(); + + let responses = join_all(futures).await; + + // First, we use filter_map to get rid of any errors and collect the + // server addresses and block counts into two vectors + let (responding_servers, block_counts_from_all_servers): (Vec<_>, Vec<_>) = + responses.clone().into_iter().filter_map(|res| res.ok()).unzip(); + + // Next, we use max to find the maximum block count from all servers + if let Some(max_block_count) = block_counts_from_all_servers.clone().iter().max() { + // Then, we use filter and collect to get the servers that have the maximum block count + let servers_with_max_count: Vec<_> = responding_servers + .into_iter() + .zip(block_counts_from_all_servers) + .filter(|(_, count)| count == max_block_count) + .map(|(addr, _)| addr) + .collect(); + + // Finally, we return a tuple of servers with max count and the max count + return Ok((servers_with_max_count, *max_block_count)); + } + + Err(MmError::new(UtxoRpcError::Internal(format!( + "Couldn't get block count from any server for {}, responses: {:?}", + &selfi.coin_ticker, responses + )))) + }; + + Box::new(fut.boxed().compat()) + } +} + +// if mockable is placed before async_trait there is `munmap_chunk(): invalid pointer` error on async fn mocking attempt +#[async_trait] +#[cfg_attr(test, mockable)] +impl UtxoRpcClientOps for ElectrumClient { + fn list_unspent(&self, address: &Address, _decimals: u8) -> UtxoRpcFut> { + let mut output_scripts = vec![try_f!(output_script(address))]; + + // If the plain pubkey is available, fetch the UTXOs found in P2PK outputs as well (if any). + if let Some(pubkey) = address.pubkey() { + let p2pk_output_script = output_script_p2pk(pubkey); + output_scripts.push(p2pk_output_script); + } + + let this = self.clone(); + let fut = async move { + let hashes = output_scripts + .iter() + .map(|s| hex::encode(electrum_script_hash(s))) + .collect(); + let unspents = this.scripthash_list_unspent_batch(hashes).compat().await?; + + let unspents = unspents + .into_iter() + .zip(output_scripts) + .flat_map(|(unspents, output_script)| { + unspents + .into_iter() + .map(move |unspent| UnspentInfo::from_electrum(unspent, output_script.clone())) + }) + .collect(); + Ok(unspents) + }; + + Box::new(fut.boxed().compat()) + } + + fn list_unspent_group(&self, addresses: Vec
, _decimals: u8) -> UtxoRpcFut { + let output_scripts = try_f!(addresses + .iter() + .map(output_script) + .collect::, keys::Error>>()); + + let this = self.clone(); + let fut = async move { + let hashes = output_scripts + .iter() + .map(|s| hex::encode(electrum_script_hash(s))) + .collect(); + let unspents = this.scripthash_list_unspent_batch(hashes).compat().await?; + + let unspents: Vec> = unspents + .into_iter() + .zip(output_scripts) + .map(|(unspents, output_script)| { + unspents + .into_iter() + .map(|unspent| UnspentInfo::from_electrum(unspent, output_script.clone())) + .collect() + }) + .collect(); + + let unspent_map = addresses + .into_iter() + // `scripthash_list_unspent_batch` returns `ScriptHashUnspents` elements in the same order in which they were requested. + // So we can zip `addresses` and `unspents` into one iterator. + .zip(unspents) + .collect(); + Ok(unspent_map) + }; + Box::new(fut.boxed().compat()) + } + + fn send_transaction(&self, tx: &UtxoTx) -> UtxoRpcFut { + let bytes = if tx.has_witness() { + BytesJson::from(serialize_with_flags(tx, SERIALIZE_TRANSACTION_WITNESS)) + } else { + BytesJson::from(serialize(tx)) + }; + Box::new( + self.blockchain_transaction_broadcast(bytes) + .map_to_mm_fut(UtxoRpcError::from), + ) + } + + fn send_raw_transaction(&self, tx: BytesJson) -> UtxoRpcFut { + Box::new( + self.blockchain_transaction_broadcast(tx) + .map_to_mm_fut(UtxoRpcError::from), + ) + } + + fn blockchain_scripthash_subscribe_using(&self, server_address: &str, scripthash: String) -> UtxoRpcFut { + Box::new( + rpc_func_from!(self, server_address, BLOCKCHAIN_SCRIPTHASH_SUB_ID, scripthash) + .map_to_mm_fut(UtxoRpcError::from), + ) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get + /// returns transaction bytes by default + fn get_transaction_bytes(&self, txid: &H256Json) -> UtxoRpcFut { + let verbose = false; + Box::new(rpc_func!(self, "blockchain.transaction.get", txid, verbose).map_to_mm_fut(UtxoRpcError::from)) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get + /// returns verbose transaction by default + fn get_verbose_transaction(&self, txid: &H256Json) -> UtxoRpcFut { + let verbose = true; + Box::new(rpc_func!(self, "blockchain.transaction.get", txid, verbose).map_to_mm_fut(UtxoRpcError::from)) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get + /// Returns verbose transactions in a batch. + fn get_verbose_transactions(&self, tx_ids: &[H256Json]) -> UtxoRpcFut> { + let verbose = true; + let requests = tx_ids + .iter() + .map(|txid| rpc_req!(self, "blockchain.transaction.get", txid, verbose)); + Box::new(self.batch_rpc(requests).map_to_mm_fut(UtxoRpcError::from)) + } + + fn get_block_count(&self) -> UtxoRpcFut { + Box::new( + self.blockchain_headers_subscribe() + .map(|r| r.block_height()) + .map_to_mm_fut(UtxoRpcError::from), + ) + } + + fn display_balance(&self, address: Address, decimals: u8) -> RpcRes { + let output_script = try_f!(output_script(&address).map_err(|err| JsonRpcError::new( + UtxoJsonRpcClientInfo::client_info(self), + rpc_req!(self, "blockchain.scripthash.get_balance").into(), + JsonRpcErrorType::Internal(err.to_string()) + ))); + let mut hashes = vec![hex::encode(electrum_script_hash(&output_script))]; + + // If the plain pubkey is available, fetch the balance found in P2PK output as well (if any). + if let Some(pubkey) = address.pubkey() { + let p2pk_output_script = output_script_p2pk(pubkey); + hashes.push(hex::encode(electrum_script_hash(&p2pk_output_script))); + } + + let this = self.clone(); + let fut = async move { + Ok(this + .scripthash_get_balances(hashes) + .compat() + .await? + .into_iter() + .fold(BigDecimal::from(0), |sum, electrum_balance| { + sum + electrum_balance.to_big_decimal(decimals) + })) + }; + Box::new(fut.boxed().compat()) + } + + fn display_balances(&self, addresses: Vec
, decimals: u8) -> UtxoRpcFut> { + let this = self.clone(); + let fut = async move { + let hashes = addresses + .iter() + .map(|address| { + let output_script = output_script(address)?; + let hash = electrum_script_hash(&output_script); + + Ok(hex::encode(hash)) + }) + .collect::, keys::Error>>()?; + + let electrum_balances = this.scripthash_get_balances(hashes).compat().await?; + let balances = electrum_balances + .into_iter() + // `scripthash_get_balances` returns `ElectrumBalance` elements in the same order in which they were requested. + // So we can zip `addresses` and the balances into one iterator. + .zip(addresses) + .map(|(electrum_balance, address)| (address, electrum_balance.to_big_decimal(decimals))) + .collect(); + Ok(balances) + }; + + Box::new(fut.boxed().compat()) + } + + fn estimate_fee_sat( + &self, + decimals: u8, + _fee_method: &EstimateFeeMethod, + mode: &Option, + n_blocks: u32, + ) -> UtxoRpcFut { + Box::new(self.estimate_fee(mode, n_blocks).map(move |fee| { + if fee > 0.00001 { + (fee * 10.0_f64.powf(decimals as f64)) as u64 + } else { + 1000 + } + })) + } + + fn get_relay_fee(&self) -> RpcRes { rpc_func!(self, "blockchain.relayfee") } + + fn find_output_spend( + &self, + tx_hash: H256, + script_pubkey: &[u8], + vout: usize, + _from_block: BlockHashOrHeight, + tx_hash_algo: TxHashAlgo, + ) -> Box, Error = String> + Send> { + let selfi = self.clone(); + let script_hash = hex::encode(electrum_script_hash(script_pubkey)); + let fut = async move { + let history = try_s!(selfi.scripthash_get_history(&script_hash).compat().await); + + if history.len() < 2 { + return Ok(None); + } + + for item in history.iter() { + let transaction = try_s!(selfi.get_transaction_bytes(&item.tx_hash).compat().await); + + let mut maybe_spend_tx: UtxoTx = + try_s!(deserialize(transaction.as_slice()).map_err(|e| ERRL!("{:?}", e))); + maybe_spend_tx.tx_hash_algo = tx_hash_algo; + drop_mutability!(maybe_spend_tx); + + for (index, input) in maybe_spend_tx.inputs.iter().enumerate() { + if input.previous_output.hash == tx_hash && input.previous_output.index == vout as u32 { + return Ok(Some(SpentOutputInfo { + input: input.clone(), + input_index: index, + spending_tx: maybe_spend_tx, + spent_in_block: BlockHashOrHeight::Height(item.height), + })); + } + } + } + Ok(None) + }; + Box::new(fut.boxed().compat()) + } + + fn get_median_time_past( + &self, + starting_block: u64, + count: NonZeroU64, + coin_variant: CoinVariant, + ) -> UtxoRpcFut { + let from = if starting_block <= count.get() { + 0 + } else { + starting_block - count.get() + 1 + }; + Box::new( + self.blockchain_block_headers(from, count) + .map_to_mm_fut(UtxoRpcError::from) + .and_then(|res| { + if res.count == 0 { + return MmError::err(UtxoRpcError::InvalidResponse("Server returned zero count".to_owned())); + } + let len = CompactInteger::from(res.count); + let mut serialized = serialize(&len).take(); + serialized.extend(res.hex.0.into_iter()); + let mut reader = Reader::new_with_coin_variant(serialized.as_slice(), coin_variant); + let headers = reader.read_list::()?; + let mut timestamps: Vec<_> = headers.into_iter().map(|block| block.time).collect(); + // can unwrap because count is non zero + Ok(median(timestamps.as_mut_slice()).unwrap()) + }), + ) + } + + async fn get_block_timestamp(&self, height: u64) -> Result> { + Ok(self.block_header_from_storage_or_rpc(height).await?.time as u64) + } +} diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection.rs new file mode 100644 index 0000000000..ca7a4bac60 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection.rs @@ -0,0 +1,734 @@ +use super::client::ElectrumClient; +use super::constants::{BLOCKCHAIN_HEADERS_SUB_ID, BLOCKCHAIN_SCRIPTHASH_SUB_ID, CUTOFF_TIMEOUT, + DEFAULT_CONNECTION_ESTABLISHMENT_TIMEOUT}; + +use crate::{RpcTransportEventHandler, SharableRpcTransportEventHandler}; +use common::custom_futures::timeout::FutureTimerExt; +use common::executor::{abortable_queue::AbortableQueue, abortable_queue::WeakSpawner, AbortableSystem, SpawnFuture, + Timer}; +use common::expirable_map::ExpirableMap; +use common::jsonrpc_client::{JsonRpcBatchResponse, JsonRpcErrorType, JsonRpcId, JsonRpcRequest, JsonRpcResponse, + JsonRpcResponseEnum}; +use common::log::{error, info}; +use common::{now_float, now_ms}; +use mm2_rpc::data::legacy::ElectrumProtocol; + +use std::io; +use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use futures::channel::oneshot as async_oneshot; +use futures::compat::{Future01CompatExt, Stream01CompatExt}; +use futures::future::FutureExt; +use futures::lock::Mutex as AsyncMutex; +use futures::select; +use futures::stream::StreamExt; +use futures01::sync::mpsc; +use futures01::{Sink, Stream}; +use http::Uri; +use instant::Instant; +use serde::Serialize; + +cfg_native! { + use super::tcp_stream::*; + + use std::convert::TryFrom; + use std::net::ToSocketAddrs; + use futures::future::{Either, TryFutureExt}; + use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, WriteHalf, ReadHalf}; + use tokio::net::TcpStream; + use tokio_rustls::{TlsConnector}; + use rustls::{ServerName}; +} + +cfg_wasm32! { + use mm2_net::wasm::wasm_ws::{ws_transport,WsOutgoingSender,WsIncomingReceiver}; + + use std::sync::atomic::AtomicUsize; +} + +pub type JsonRpcPendingRequests = ExpirableMap>; + +macro_rules! disconnect_and_return { + ($typ:tt, $err:expr, $conn:expr, $handlers:expr) => {{ + let err = ElectrumConnectionErr::$typ(format!("{:?}", $err)); + disconnect_and_return!(err, $conn, $handlers); + }}; + ($err:expr, $conn:expr, $handlers:expr) => {{ + // Inform the event handlers of the disconnection. + $handlers.on_disconnected(&$conn.address()).ok(); + // Disconnect the connection. + $conn.disconnect(Some($err.clone())); + return Err($err); + }}; +} + +macro_rules! disconnect_and_return_if_err { + ($ex:expr, $typ:tt, $conn:expr, $handlers:expr) => {{ + match $ex { + Ok(res) => res, + Err(e) => { + disconnect_and_return!($typ, e, $conn, $handlers); + }, + } + }}; + ($ex:expr, $conn:expr, $handlers:expr) => {{ + match $ex { + Ok(res) => res, + Err(e) => { + disconnect_and_return!(e, $conn, $handlers); + }, + } + }}; +} + +macro_rules! wrap_timeout { + ($call:expr, $timeout:expr, $conn:expr, $handlers:expr) => {{ + let now = Instant::now(); + let res = match $call.timeout_secs($timeout).await { + Ok(res) => res, + Err(_) => { + disconnect_and_return!( + ElectrumConnectionErr::Timeout(stringify!($call), $timeout), + $conn, + $handlers + ); + }, + }; + // Remaining timeout after executing `$call`. + let timeout = ($timeout - now.elapsed().as_secs_f64()).max(0.0); + (timeout, res) + }}; +} + +/// Helper function casting mpsc::Receiver as Stream. +fn rx_to_stream(rx: mpsc::Receiver>) -> impl Stream, Error = io::Error> { + rx.map_err(|_| panic!("errors not possible on rx")) +} + +#[cfg(not(target_arch = "wasm32"))] +/// Helper function to parse a a string DNS name into a ServerName. +fn server_name_from_domain(dns_name: &str) -> Result { + match ServerName::try_from(dns_name) { + // The `ServerName` must be `DnsName` variant, SSL works with domain names and not IPs. + Ok(dns_name) if matches!(dns_name, ServerName::DnsName(_)) => Ok(dns_name), + _ => ERR!("Couldn't parse DNS name from '{}'", dns_name), + } +} + +/// Electrum request RPC representation +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ElectrumConnectionSettings { + pub url: String, + #[serde(default)] + pub protocol: ElectrumProtocol, + #[serde(default)] + pub disable_cert_verification: bool, + pub timeout_sec: Option, +} + +/// Possible connection errors when connection to an Electrum server. +#[derive(Clone, Debug)] +pub enum ElectrumConnectionErr { + /// Couldn't connect to the server within the provided timeout. + /// The first argument is the call (stringified) that timed out. + /// The second argument is the time limit it had to finish within, in seconds. + Timeout(&'static str, f64), + /// A temporary error that might be resolved later on. + Temporary(String), + /// An error that can't be resolved by retrying. + Irrecoverable(String), + /// The server's version doesn't match the client's version. + VersionMismatch(String), +} + +impl ElectrumConnectionErr { + pub fn is_recoverable(&self) -> bool { + match self { + ElectrumConnectionErr::Irrecoverable(_) | ElectrumConnectionErr::VersionMismatch(_) => false, + ElectrumConnectionErr::Timeout(_, _) | ElectrumConnectionErr::Temporary(_) => true, + } + } +} + +/// Represents the active Electrum connection to selected address +#[derive(Debug)] +pub struct ElectrumConnection { + /// The client connected to this SocketAddr + settings: ElectrumConnectionSettings, + /// The Sender forwarding requests to writing part of underlying stream + tx: Mutex>>>, + /// A lock to prevent multiple connection establishments happening concurrently. + establishing_connection: AsyncMutex<()>, + /// Responses are stored here + responses: Mutex, + /// Selected protocol version. The value is initialized after the server.version RPC call. + protocol_version: Mutex>, + /// Why was the connection disconnected the last time? + last_error: Mutex>, + /// An abortable system for connection specific tasks to run on. + abortable_system: AbortableQueue, +} + +impl ElectrumConnection { + pub fn new(settings: ElectrumConnectionSettings, abortable_system: AbortableQueue) -> Self { + ElectrumConnection { + settings, + tx: Mutex::new(None), + establishing_connection: AsyncMutex::new(()), + responses: Mutex::new(JsonRpcPendingRequests::new()), + protocol_version: Mutex::new(None), + last_error: Mutex::new(None), + abortable_system, + } + } + + pub fn address(&self) -> &str { &self.settings.url } + + fn weak_spawner(&self) -> WeakSpawner { self.abortable_system.weak_spawner() } + + fn is_connected(&self) -> bool { self.tx.lock().unwrap().is_some() } + + fn set_protocol_version(&self, version: f32) { + let mut protocol_version = self.protocol_version.lock().unwrap(); + if protocol_version.is_none() { + *protocol_version = Some(version); + } + } + + fn clear_protocol_version(&self) { self.protocol_version.lock().unwrap().take(); } + + fn set_last_error(&self, reason: ElectrumConnectionErr) { + let mut last_error = self.last_error.lock().unwrap(); + if last_error.is_none() { + *last_error = Some(reason); + } + } + + fn clear_last_error(&self) { self.last_error.lock().unwrap().take(); } + + fn last_error(&self) -> Option { self.last_error.lock().unwrap().clone() } + + /// Connects to the electrum server by setting the `tx` sender channel. + /// + /// # Safety: + /// For this to be atomic, the caller must have acquired the lock to `establishing_connection`. + fn connect(&self, tx: mpsc::Sender>) { + self.tx.lock().unwrap().replace(tx); + self.clear_last_error(); + } + + /// Disconnect and clear the connection state. + pub fn disconnect(&self, reason: Option) { + self.tx.lock().unwrap().take(); + self.responses.lock().unwrap().clear(); + self.clear_protocol_version(); + if let Some(reason) = reason { + self.set_last_error(reason); + } + self.abortable_system.abort_all_and_reset().ok(); + } + + /// Sends a request to the electrum server and waits for the response. + /// + /// ## Important: This should always return [`JsonRpcErrorType::Transport`] error. + pub async fn electrum_request( + &self, + mut req_json: String, + rpc_id: JsonRpcId, + timeout: f64, + ) -> Result { + #[cfg(not(target_arch = "wasm"))] + { + // Electrum request and responses must end with \n + // https://electrumx.readthedocs.io/en/latest/protocol-basics.html#message-stream + req_json.push('\n'); + } + + // Create a oneshot channel to receive the response in. + let (req_tx, res_rx) = async_oneshot::channel(); + self.responses + .lock() + .unwrap() + .insert(rpc_id, req_tx, Duration::from_secs_f64(timeout)); + let tx = self + .tx + .lock() + .unwrap() + // Clone to not to hold the lock while sending the request. + .clone() + .ok_or_else(|| JsonRpcErrorType::Transport("Connection is not established".to_string()))?; + + // Send the request to the electrum server. + tx.send(req_json.into_bytes()) + .compat() + .await + .map_err(|e| JsonRpcErrorType::Transport(e.to_string()))?; + + // Wait for the response to be processed and sent back to us. + res_rx + .timeout_secs(timeout) + .await + .map_err(|e| JsonRpcErrorType::Transport(e.to_string()))? + .map_err(|_e| JsonRpcErrorType::Transport("The sender didn't send".to_string())) + } + + /// Process an incoming JSONRPC response from the electrum server. + fn process_electrum_response(&self, bytes: &[u8], event_handlers: &Vec>) { + // Inform the event handlers. + event_handlers.on_incoming_response(bytes); + + // detect if we got standard JSONRPC response or subscription response as JSONRPC request + #[derive(Deserialize)] + #[serde(untagged)] + enum ElectrumRpcResponseEnum { + /// The subscription response as JSONRPC request. + /// + /// NOTE Because JsonRpcResponse uses default values for each of its field, + /// this variant has to stay at top in this enumeration to be properly deserialized + /// from serde. + SubscriptionNotification(JsonRpcRequest), + /// The standard JSONRPC single response. + SingleResponse(JsonRpcResponse), + /// The batch of standard JSONRPC responses. + BatchResponses(JsonRpcBatchResponse), + } + + let response: ElectrumRpcResponseEnum = match serde_json::from_slice(bytes) { + Ok(res) => res, + Err(e) => { + error!("{}", e); + return; + }, + }; + + let response = match response { + ElectrumRpcResponseEnum::SingleResponse(single) => JsonRpcResponseEnum::Single(single), + ElectrumRpcResponseEnum::BatchResponses(batch) => JsonRpcResponseEnum::Batch(batch), + ElectrumRpcResponseEnum::SubscriptionNotification(req) => { + match req.method.as_str() { + // NOTE: Sending a script hash notification is handled in it's own event handler. + BLOCKCHAIN_SCRIPTHASH_SUB_ID | BLOCKCHAIN_HEADERS_SUB_ID => {}, + _ => { + error!("Unexpected notification method: {}", req.method); + }, + } + return; + }, + }; + + // the corresponding sender may not exist, receiver may be dropped + // these situations are not considered as errors so we just silently skip them + let pending = self.responses.lock().unwrap().remove(&response.rpc_id()); + if let Some(tx) = pending { + tx.send(response).ok(); + } + } + + /// Process a bulk response from the electrum server. + /// + /// A bulk response is a response that contains multiple JSONRPC responses. + fn process_electrum_bulk_response( + &self, + bulk_response: &[u8], + event_handlers: &Vec>, + ) { + // We should split the received response because we can get several responses in bulk. + let responses = bulk_response.split(|item| *item == b'\n'); + + for response in responses { + // `split` returns empty slice if it ends with separator which is our case. + if !response.is_empty() { + self.process_electrum_response(response, event_handlers) + } + } + } +} + +// Connection loop establishment methods. +impl ElectrumConnection { + /// Tries to establish a connection to the server. + /// + /// Returns the tokio stream with the server and the remaining timeout + /// left from the input timeout. + #[cfg(not(target_arch = "wasm32"))] + async fn establish_connection(connection: &ElectrumConnection) -> Result { + let address = connection.address(); + + let socket_addr = match address.to_socket_addrs() { + Err(e) if matches!(e.kind(), std::io::ErrorKind::InvalidInput) => { + return Err(ElectrumConnectionErr::Irrecoverable(format!( + "Invalid address format: {e:?}" + ))); + }, + Err(e) => { + return Err(ElectrumConnectionErr::Temporary(format!( + "Resolve error in address: {e:?}" + ))); + }, + Ok(mut addr) => match addr.next() { + None => { + return Err(ElectrumConnectionErr::Temporary("Address resolved to None".to_string())); + }, + Some(addr) => addr, + }, + }; + + let connect_f = match connection.settings.protocol { + ElectrumProtocol::TCP => Either::Left(TcpStream::connect(&socket_addr).map_ok(ElectrumStream::Tcp)), + ElectrumProtocol::SSL => { + let uri: Uri = match address.parse() { + Ok(uri) => uri, + Err(e) => { + return Err(ElectrumConnectionErr::Irrecoverable(format!("URL parse error: {e:?}"))); + }, + }; + + let Some(dns_name) = uri.host().map(String::from) else { + return Err(ElectrumConnectionErr::Irrecoverable( + "Couldn't retrieve host from address".to_string(), + )); + }; + + let Ok(dns) = server_name_from_domain(dns_name.as_str()) else { + return Err(ElectrumConnectionErr::Irrecoverable( + "Address isn't a valid domain name".to_string(), + )); + }; + + let tls_connector = if connection.settings.disable_cert_verification { + TlsConnector::from(UNSAFE_TLS_CONFIG.clone()) + } else { + TlsConnector::from(SAFE_TLS_CONFIG.clone()) + }; + + Either::Right( + TcpStream::connect(&socket_addr) + .and_then(move |stream| tls_connector.connect(dns, stream).map_ok(ElectrumStream::Tls)), + ) + }, + ElectrumProtocol::WS | ElectrumProtocol::WSS => { + return Err(ElectrumConnectionErr::Irrecoverable( + "Incorrect protocol for native connection ('WS'/'WSS'). Use 'TCP' or 'SSL' instead.".to_string(), + )); + }, + }; + + // Try to connect to the server. + let stream = match connect_f.await { + Ok(stream) => stream, + Err(e) => { + return Err(ElectrumConnectionErr::Temporary(format!( + "Couldn't connect to the electrum server: {e:?}" + ))) + }, + }; + if let Err(e) = stream.as_ref().set_nodelay(true) { + return Err(ElectrumConnectionErr::Temporary(format!( + "Setting TCP_NODELAY failed: {e:?}" + ))); + }; + + Ok(stream) + } + + #[cfg(target_arch = "wasm32")] + async fn establish_connection( + connection: &ElectrumConnection, + ) -> Result<(WsIncomingReceiver, WsOutgoingSender), ElectrumConnectionErr> { + lazy_static! { + static ref CONN_IDX: Arc = Arc::new(AtomicUsize::new(0)); + } + + let address = connection.address(); + let uri: Uri = match address.parse() { + Ok(uri) => uri, + Err(e) => { + return Err(ElectrumConnectionErr::Irrecoverable(format!( + "Failed to parse the address: {e:?}" + ))); + }, + }; + if uri.scheme().is_some() { + return Err(ElectrumConnectionErr::Irrecoverable( + "There has not to be a scheme in the url. 'ws://' scheme is used by default. Consider using 'protocol: \"WSS\"' in the electrum request to switch to the 'wss://' scheme.".to_string(), + ) + ); + } + + let protocol_prefixed_address = match connection.settings.protocol { + ElectrumProtocol::WS => { + format!("ws://{address}") + }, + ElectrumProtocol::WSS => { + format!("wss://{address}") + }, + ElectrumProtocol::TCP | ElectrumProtocol::SSL => { + return Err(ElectrumConnectionErr::Irrecoverable( + "'TCP' and 'SSL' are not supported in a browser. Please use 'WS' or 'WSS' protocols".to_string(), + )); + }, + }; + + let spawner = connection.weak_spawner(); + let connect_f = ws_transport( + CONN_IDX.fetch_add(1, AtomicOrdering::Relaxed), + &protocol_prefixed_address, + &spawner, + ); + + // Try to connect to the server. + let (transport_tx, transport_rx) = match connect_f.await { + Ok(stream) => stream, + Err(e) => { + return Err(ElectrumConnectionErr::Temporary(format!( + "Couldn't connect to the electrum server: {e:?}" + ))) + }, + }; + + Ok((transport_rx, transport_tx)) + } + + /// Waits until `last_response` time is too old in the past then returns a temporary error. + async fn timeout_loop(last_response: Arc) -> ElectrumConnectionErr { + loop { + Timer::sleep(CUTOFF_TIMEOUT).await; + let last_sec = (last_response.load(AtomicOrdering::Relaxed) / 1000) as f64; + if now_float() - last_sec > CUTOFF_TIMEOUT { + break ElectrumConnectionErr::Temporary(format!( + "Server didn't respond for too long ({}s).", + now_float() - last_sec + )); + } + } + } + + /// Runs the send loop that sends outgoing requests to the server. + /// + /// This runs until the sender is disconnected. + async fn send_loop( + address: String, + event_handlers: Arc>>, + #[cfg(not(target_arch = "wasm32"))] mut write: WriteHalf, + #[cfg(target_arch = "wasm32")] mut write: WsOutgoingSender, + rx: mpsc::Receiver>, + ) -> ElectrumConnectionErr { + let mut rx = rx_to_stream(rx).compat(); + while let Some(Ok(bytes)) = rx.next().await { + // NOTE: We shouldn't really notify on going request yet since we don't know + // if sending will error. We do that early though to avoid cloning the bytes on wasm. + event_handlers.on_outgoing_request(&bytes); + + #[cfg(not(target_arch = "wasm32"))] + let send_result = write.write_all(&bytes).await; + #[cfg(target_arch = "wasm32")] + let send_result = write.send(bytes).await; + + if let Err(e) = send_result { + error!("Write error {e} to {address}"); + } + } + ElectrumConnectionErr::Temporary("Sender disconnected".to_string()) + } + + /// Runs the receive loop that reads incoming responses from the server. + /// + /// This runs until the electrum server sends an empty response (signaling disconnection), + /// or if we encounter an error while reading from the stream. + #[cfg(not(target_arch = "wasm32"))] + async fn recv_loop( + connection: Arc, + event_handlers: Arc>>, + read: ReadHalf, + last_response: Arc, + ) -> ElectrumConnectionErr { + let mut buffer = String::with_capacity(1024); + let mut buf_reader = BufReader::new(read); + loop { + match buf_reader.read_line(&mut buffer).await { + Ok(c) => { + if c == 0 { + break ElectrumConnectionErr::Temporary("EOF".to_string()); + } + }, + Err(e) => { + break ElectrumConnectionErr::Temporary(format!("Error on read {e:?}")); + }, + }; + + last_response.store(now_ms(), AtomicOrdering::Relaxed); + connection.process_electrum_bulk_response(buffer.as_bytes(), &event_handlers); + buffer.clear(); + } + } + + #[cfg(target_arch = "wasm32")] + async fn recv_loop( + connection: Arc, + event_handlers: Arc>>, + mut read: WsIncomingReceiver, + last_response: Arc, + ) -> ElectrumConnectionErr { + let address = connection.address(); + while let Some(response) = read.next().await { + match response { + Ok(bytes) => { + last_response.store(now_ms(), AtomicOrdering::Relaxed); + connection.process_electrum_response(&bytes, &event_handlers); + }, + Err(e) => { + error!("{address} error: {e:?}"); + }, + } + } + ElectrumConnectionErr::Temporary("Receiver disconnected".to_string()) + } + + /// Checks the server version against the range of accepted versions and disconnects the server + /// if the version is not supported. + async fn check_server_version( + connection: &ElectrumConnection, + client: &ElectrumClient, + ) -> Result<(), ElectrumConnectionErr> { + let address = connection.address(); + + // Don't query for the version if the client doesn't care about it, as querying for the version might + // fail with the protocol range we will provide. + if !client.negotiate_version() { + return Ok(()); + } + + match client.server_version(address, client.protocol_version()).compat().await { + Ok(version_str) => match version_str.protocol_version.parse::() { + Ok(version_f32) => { + connection.set_protocol_version(version_f32); + Ok(()) + }, + Err(e) => Err(ElectrumConnectionErr::Temporary(format!( + "Failed to parse electrum server version {e:?}" + ))), + }, + // If the version we provided isn't supported by the server, it returns a JSONRPC response error. + Err(e) if matches!(e.error, JsonRpcErrorType::Response(..)) => { + Err(ElectrumConnectionErr::VersionMismatch(format!("{e:?}"))) + }, + Err(e) => Err(ElectrumConnectionErr::Temporary(format!( + "Failed to get electrum server version {e:?}" + ))), + } + } + + /// Starts the connection loop that keeps an active connection to the electrum server. + /// If this connection is already connected, nothing is performed and `Ok(())` is returned. + /// + /// This will first try to connect to the server and use that connection to query its version. + /// If version checks succeed, the connection will be kept alive, otherwise, it will be terminated. + pub async fn establish_connection_loop( + self: &Arc, + client: ElectrumClient, + ) -> Result<(), ElectrumConnectionErr> { + let connection = self.clone(); + let address = connection.address().to_string(); + let event_handlers = client.event_handlers(); + // This is the timeout for connection establishment and version querying (i.e. the whole method). + // The caller is guaranteed that the method will return within this time. + let timeout = connection + .settings + .timeout_sec + .unwrap_or(DEFAULT_CONNECTION_ESTABLISHMENT_TIMEOUT); + + // Locking `establishing_connection` will prevent other threads from establishing a connection concurrently. + let (timeout, _establishing_connection) = wrap_timeout!( + connection.establishing_connection.lock(), + timeout, + connection, + event_handlers + ); + + // Check if we are already connected. + if connection.is_connected() { + return Ok(()); + } + + // Check why we errored the last time, don't try to reconnect if it was an irrecoverable error. + if let Some(last_error) = connection.last_error() { + if !last_error.is_recoverable() { + return Err(last_error); + } + } + + let (timeout, stream_res) = wrap_timeout!( + Self::establish_connection(&connection).boxed(), + timeout, + connection, + event_handlers + ); + let stream = disconnect_and_return_if_err!(stream_res, connection, event_handlers); + + let (connection_ready_signal, wait_for_connection_ready) = async_oneshot::channel(); + let connection_loop = { + // Branch 1: Disconnect after not receiving responses for too long. + let last_response = Arc::new(AtomicU64::new(now_ms())); + let timeout_branch = Self::timeout_loop(last_response.clone()).boxed(); + + // Branch 2: Read incoming responses from the server. + #[cfg(not(target_arch = "wasm32"))] + let (read, write) = tokio::io::split(stream); + #[cfg(target_arch = "wasm32")] + let (read, write) = stream; + let recv_branch = Self::recv_loop(connection.clone(), event_handlers.clone(), read, last_response).boxed(); + + // Branch 3: Send outgoing requests to the server. + let (tx, rx) = mpsc::channel(0); + let send_branch = Self::send_loop(address.clone(), event_handlers.clone(), write, rx).boxed(); + + let connection = connection.clone(); + let event_handlers = event_handlers.clone(); + async move { + connection.connect(tx); + // Signal that the connection is up and ready so to start the version querying. + connection_ready_signal.send(()).ok(); + event_handlers.on_connected(&address).ok(); + let via = match connection.settings.protocol { + ElectrumProtocol::TCP => "via TCP", + ElectrumProtocol::SSL if connection.settings.disable_cert_verification => { + "via SSL *with disabled certificate verification*" + }, + ElectrumProtocol::SSL => "via SSL", + ElectrumProtocol::WS => "via WS", + ElectrumProtocol::WSS => "via WSS", + }; + info!("{address} is now connected {via}."); + + let err = select! { + e = timeout_branch.fuse() => e, + e = recv_branch.fuse() => e, + e = send_branch.fuse() => e, + }; + + error!("{address} connection dropped due to: {err:?}"); + event_handlers.on_disconnected(&address).ok(); + connection.disconnect(Some(err)); + } + }; + // Start the connection loop on a weak spawner. + connection.weak_spawner().spawn(connection_loop); + + // Wait for the connection to be ready before querying the version. + let (timeout, connection_ready_res) = + wrap_timeout!(wait_for_connection_ready, timeout, connection, event_handlers); + disconnect_and_return_if_err!(connection_ready_res, Temporary, connection, event_handlers); + + let (_, version_res) = wrap_timeout!( + Self::check_server_version(&connection, &client).boxed(), + timeout, + connection, + event_handlers + ); + disconnect_and_return_if_err!(version_res, connection, event_handlers); + + Ok(()) + } +} diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/connection_context.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/connection_context.rs new file mode 100644 index 0000000000..17f3495b85 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/connection_context.rs @@ -0,0 +1,91 @@ +use std::collections::HashSet; +use std::mem; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Arc, Mutex}; + +use super::super::connection::ElectrumConnection; +use super::super::constants::FIRST_SUSPEND_TIME; + +use common::now_ms; +use keys::Address; + +#[derive(Debug)] +struct SuspendTimer { + /// When was the connection last disconnected. + disconnected_at: AtomicU64, + /// How long to suspend the server the next time it disconnects (in milliseconds). + next_suspend_time: AtomicU64, +} + +impl SuspendTimer { + /// Creates a new suspend timer. + fn new() -> Self { + SuspendTimer { + disconnected_at: AtomicU64::new(0), + next_suspend_time: AtomicU64::new(FIRST_SUSPEND_TIME), + } + } + + /// Resets the suspend time and disconnection time. + fn reset(&self) { + self.disconnected_at.store(0, Ordering::SeqCst); + self.next_suspend_time.store(FIRST_SUSPEND_TIME, Ordering::SeqCst); + } + + /// Doubles the suspend time and sets the disconnection time to `now`. + fn double(&self) { + // The max suspend time, 12h. + const MAX_SUSPEND_TIME: u64 = 12 * 60 * 60; + self.disconnected_at.store(now_ms(), Ordering::SeqCst); + let mut next_suspend_time = self.next_suspend_time.load(Ordering::SeqCst); + next_suspend_time = (next_suspend_time * 2).min(MAX_SUSPEND_TIME); + self.next_suspend_time.store(next_suspend_time, Ordering::SeqCst); + } + + /// Returns the time until when the server should be suspended in milliseconds. + fn get_suspend_until(&self) -> u64 { + self.disconnected_at.load(Ordering::SeqCst) + self.next_suspend_time.load(Ordering::SeqCst) * 1000 + } +} + +/// A struct that encapsulates an Electrum connection and its information. +#[derive(Debug)] +pub struct ConnectionContext { + /// The electrum connection. + pub connection: Arc, + /// The list of addresses subscribed to the connection. + subs: Mutex>, + /// The timer deciding when the connection is ready to be used again. + suspend_timer: SuspendTimer, + /// The ID of this connection which also serves as a priority (lower is better). + pub id: u32, +} + +impl ConnectionContext { + /// Creates a new connection context. + pub(super) fn new(connection: ElectrumConnection, id: u32) -> Self { + ConnectionContext { + connection: Arc::new(connection), + subs: Mutex::new(HashSet::new()), + suspend_timer: SuspendTimer::new(), + id, + } + } + + /// Resets the suspend time. + pub(super) fn connected(&self) { self.suspend_timer.reset(); } + + /// Inform the connection context that the connection has been disconnected. + /// + /// Doubles the suspend time and clears the subs list and returns it. + pub(super) fn disconnected(&self) -> HashSet
{ + self.suspend_timer.double(); + mem::take(&mut self.subs.lock().unwrap()) + } + + /// Returns the time the server should be suspended until (when to take it up) in milliseconds. + pub(super) fn suspended_till(&self) -> u64 { self.suspend_timer.get_suspend_until() } + + /// Adds a subscription to the connection context. + pub(super) fn add_sub(&self, address: Address) { self.subs.lock().unwrap().insert(address); } +} diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/manager.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/manager.rs new file mode 100644 index 0000000000..b06628fd60 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/manager.rs @@ -0,0 +1,528 @@ +use std::collections::{BTreeMap, HashMap}; +use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak}; + +use super::super::client::{ElectrumClient, ElectrumClientImpl}; +use super::super::connection::{ElectrumConnection, ElectrumConnectionErr, ElectrumConnectionSettings}; +use super::super::constants::{BACKGROUND_TASK_WAIT_TIMEOUT, PING_INTERVAL}; +use super::connection_context::ConnectionContext; + +use crate::utxo::rpc_clients::UtxoRpcClientOps; +use common::executor::abortable_queue::AbortableQueue; +use common::executor::{AbortableSystem, SpawnFuture, Timer}; +use common::log::{debug, error}; +use common::notifier::{Notifiee, Notifier}; +use common::now_ms; +use keys::Address; + +use futures::compat::Future01CompatExt; +use futures::FutureExt; + +/// A macro to unwrap an option and *execute* some code if the option is None. +macro_rules! unwrap_or_else { + ($option:expr, $($action:tt)*) => {{ + match $option { + Some(some_val) => some_val, + None => { $($action)* } + } + }}; +} + +macro_rules! unwrap_or_continue { + ($option:expr) => { + unwrap_or_else!($option, continue) + }; +} + +macro_rules! unwrap_or_return { + ($option:expr, $ret:expr) => { + unwrap_or_else!($option, return $ret) + }; + ($option:expr) => { + unwrap_or_else!($option, return) + }; +} + +/// The ID of a connection (and also its priority, lower is better). +type ID = u32; + +#[derive(Debug, Display)] +pub enum ConnectionManagerErr { + #[display(fmt = "Unknown server address")] + UnknownAddress, + #[display(fmt = "Failed to connect to the server due to {:?}", _0)] + ConnectingError(ElectrumConnectionErr), + #[display(fmt = "No client found, connection manager isn't initialized properly")] + NoClient, + #[display(fmt = "Connection manager is already initialized")] + AlreadyInitialized, +} + +/// The configuration parameter for a connection manager. +#[derive(Debug)] +pub struct ManagerConfig { + /// A flag to spawn a ping loop task for active connections. + pub spawn_ping: bool, + /// The minimum number of connections that should be connected at all times. + pub min_connected: usize, + /// The maximum number of connections that can be connected at any given time. + pub max_connected: usize, +} + +#[derive(Debug)] +/// A connection manager that maintains a set of connections to electrum servers and +/// handles reconnecting, address subscription distribution, etc... +struct ConnectionManagerImpl { + /// The configuration for the connection manager. + config: ManagerConfig, + /// The set of addresses that are currently connected. + /// + /// This set's size should satisfy: `min_connected <= maintained_connections.len() <= max_connected`. + /// + /// It is actually represented as a sorted map from connection ID (u32, also represents connection priority) + /// to address so we can easily/cheaply pop low priority connections and add high priority ones. + maintained_connections: RwLock>, + /// A map for server addresses to their corresponding connections. + connections: RwLock>, + /// A weak reference to the electrum client that owns this connection manager. + /// It is used to send electrum requests during connection establishment (version querying). + // TODO: This field might not be necessary if [`ElectrumConnection`] object be used to send + // electrum requests on its own, i.e. implement [`JsonRpcClient`] & [`UtxoRpcClientOps`]. + electrum_client: RwLock>>, + /// A notification sender to notify the background task when we have less than `min_connected` connections. + below_min_connected_notifier: Notifier, + /// A notification receiver to be used by the background task to receive notifications of when + /// we have less than `min_connected` maintained connections. + /// + /// Wrapped inside a Mutex>, +} + +#[derive(Clone, Debug)] +pub struct ConnectionManager(Arc); + +// Public interface. +impl ConnectionManager { + pub fn try_new( + servers: Vec, + spawn_ping: bool, + (min_connected, max_connected): (usize, usize), + abortable_system: &AbortableQueue, + ) -> Result { + let mut connections = HashMap::with_capacity(servers.len()); + // Priority is assumed to be the order of the servers in the list as they appear. + for (priority, connection_settings) in servers.into_iter().enumerate() { + let subsystem = abortable_system.create_subsystem().map_err(|e| { + ERRL!( + "Failed to create abortable subsystem for connection: {}, error: {:?}", + connection_settings.url, + e + ) + })?; + let connection = ElectrumConnection::new(connection_settings, subsystem); + connections.insert( + connection.address().to_string(), + ConnectionContext::new(connection, priority as u32), + ); + } + + if min_connected == 0 { + return Err(ERRL!("min_connected should be greater than 0")); + } + if min_connected > max_connected { + return Err(ERRL!( + "min_connected ({}) must be <= max_connected ({})", + min_connected, + max_connected + )); + } + + let (notifier, notifiee) = Notifier::new(); + Ok(ConnectionManager(Arc::new(ConnectionManagerImpl { + config: ManagerConfig { + spawn_ping, + min_connected, + max_connected, + }, + connections: RwLock::new(connections), + maintained_connections: RwLock::new(BTreeMap::new()), + electrum_client: RwLock::new(None), + below_min_connected_notifier: notifier, + below_min_connected_notifiee: Mutex::new(Some(notifiee)), + }))) + } + + /// Initializes the connection manager by connecting the electrum connections. + /// This must be called and only be called once to have a functioning connection manager. + pub fn initialize(&self, weak_client: Weak) -> Result<(), ConnectionManagerErr> { + // Disallow reusing the same manager with another client. + if self.weak_client().read().unwrap().is_some() { + return Err(ConnectionManagerErr::AlreadyInitialized); + } + + let electrum_client = unwrap_or_return!(weak_client.upgrade(), Err(ConnectionManagerErr::NoClient)); + + // Store the (weak) electrum client. + *self.weak_client().write().unwrap() = Some(weak_client); + + // Use the client's spawner to spawn the connection manager's background task. + electrum_client.weak_spawner().spawn(self.clone().background_task()); + + if self.config().spawn_ping { + // Use the client's spawner to spawn the connection manager's ping task. + electrum_client.weak_spawner().spawn(self.clone().ping_task()); + } + + Ok(()) + } + + /// Returns all the server addresses. + pub fn get_all_server_addresses(&self) -> Vec { self.read_connections().keys().cloned().collect() } + + /// Returns all the connections. + pub fn get_all_connections(&self) -> Vec> { + self.read_connections() + .values() + .map(|conn_ctx| conn_ctx.connection.clone()) + .collect() + } + + /// Retrieve a specific electrum connection by its address. + /// The connection will be forcibly established if it's disconnected. + pub async fn get_connection_by_address( + &self, + server_address: &str, + force_connect: bool, + ) -> Result, ConnectionManagerErr> { + let connection = self + .get_connection(server_address) + .ok_or(ConnectionManagerErr::UnknownAddress)?; + + if force_connect { + let client = unwrap_or_return!(self.get_client(), Err(ConnectionManagerErr::NoClient)); + // Make sure the connection is connected. + connection + .establish_connection_loop(client) + .await + .map_err(ConnectionManagerErr::ConnectingError)?; + } + + Ok(connection) + } + + /// Returns a list of active/maintained connections. + pub fn get_active_connections(&self) -> Vec> { + self.read_maintained_connections() + .iter() + .filter_map(|(_id, address)| self.get_connection(address)) + .collect() + } + + /// Returns a boolean `true` if the connection pool is empty, `false` otherwise. + pub fn is_connections_pool_empty(&self) -> bool { self.read_connections().is_empty() } + + /// Subscribe the list of addresses to our active connections. + /// + /// There is a bit of indirection here. We register the abandoned addresses on `on_disconnected` with + /// the client to queue them for `utxo_balance_events` which in turn calls this method back to re-subscribe + /// the abandoned addresses. We could have instead directly re-subscribed the addresses here in the connection + /// manager without sending them to `utxo_balance_events`. However, we don't do that so that `utxo_balance_events` + /// knows about all the added addresses. If it doesn't know about them, it won't be able to retrieve the triggered + /// address when its script hash is notified. + pub async fn add_subscriptions(&self, addresses: &HashMap) { + for (scripthash, address) in addresses.iter() { + // For a single address/scripthash, keep trying to subscribe it until we succeed. + 'single_address_sub: loop { + let client = unwrap_or_return!(self.get_client()); + let connections = self.get_active_connections(); + if connections.is_empty() { + // If there are no active connections, wait for a connection to be established. + Timer::sleep(1.).await; + continue; + } + // Try to subscribe the address to any connection we have. + for connection in connections { + if client + .blockchain_scripthash_subscribe_using(connection.address(), scripthash.clone()) + .compat() + .await + .is_ok() + { + let all_connections = self.read_connections(); + let connection_ctx = unwrap_or_continue!(all_connections.get(connection.address())); + connection_ctx.add_sub(address.clone()); + break 'single_address_sub; + } + } + } + } + } + + /// Handles the connection event. + pub fn on_connected(&self, server_address: &str) { + let all_connections = self.read_connections(); + let connection_ctx = unwrap_or_return!(all_connections.get(server_address)); + + // Reset the suspend time & disconnection time. + connection_ctx.connected(); + } + + /// Handles the disconnection event from an Electrum server. + pub fn on_disconnected(&self, server_address: &str) { + debug!("Electrum server disconnected: {}", server_address); + let all_connections = self.read_connections(); + let connection_ctx = unwrap_or_return!(all_connections.get(server_address)); + + self.unmaintain(connection_ctx.id); + + let abandoned_subs = connection_ctx.disconnected(); + // Re-subscribe the abandoned addresses using the client. + let client = unwrap_or_return!(self.get_client()); + client.subscribe_addresses(abandoned_subs).ok(); + } + + /// A method that should be called after using a specific server for some request. + /// + /// Instead of disconnecting the connection right away, this method will only disconnect it + /// if it's not in the maintained connections set. + pub fn not_needed(&self, server_address: &str) { + let (id, connection) = { + let all_connections = self.read_connections(); + let connection_ctx = unwrap_or_return!(all_connections.get(server_address)); + (connection_ctx.id, connection_ctx.connection.clone()) + }; + if !self.read_maintained_connections().contains_key(&id) { + connection.disconnect(Some(ElectrumConnectionErr::Temporary("Not needed anymore".to_string()))); + self.on_disconnected(connection.address()); + } + } + + /// Remove a connection from the connection manager by its address. + // TODO(feat): Add the ability to add a connection during runtime. + pub fn remove_connection(&self, server_address: &str) -> Result, ConnectionManagerErr> { + let connection = self + .get_connection(server_address) + .ok_or(ConnectionManagerErr::UnknownAddress)?; + // Make sure this connection is disconnected. + connection.disconnect(Some(ElectrumConnectionErr::Irrecoverable( + "Forcefully disconnected & removed".to_string(), + ))); + // Run the on-disconnection hook, this will also make sure the connection is removed from the maintained set. + self.on_disconnected(connection.address()); + // Remove the connection from the manager. + self.write_connections().remove(server_address); + Ok(connection) + } +} + +// Background tasks. +impl ConnectionManager { + /// A forever-lived task that pings active/maintained connections periodically. + async fn ping_task(self) { + loop { + let client = unwrap_or_return!(self.get_client()); + // This will ping all the active/maintained connections, which will keep these connections alive. + client.server_ping().compat().await.ok(); + Timer::sleep(PING_INTERVAL).await; + } + } + + /// A forever-lived task that does the house keeping tasks of the connection manager: + /// - Maintaining the right number of active connections. + /// - Establishing new connections if needed. + /// - Replacing low priority connections with high priority ones periodically. + /// - etc... + async fn background_task(self) { + // Take out the min_connected notifiee from the manager. + let mut min_connected_notification = unwrap_or_return!(self.extract_below_min_connected_notifiee()); + // A flag to indicate whether to log connection establishment errors or not. We should not log them if we + // are in panic mode (i.e. we are below the `min_connected` threshold) as this will flood the error log. + let mut log_errors = true; + loop { + // Get the candidate connections that we will consider maintaining. + let (candidate_connections, will_never_get_min_connected) = self.get_candidate_connections(); + // Establish the connections to the selected candidates and alter the maintained connections set accordingly. + self.establish_best_connections(candidate_connections, log_errors).await; + // Only sleep if we successfully acquired the minimum number of connections, + // or if we know we can never maintain `min_connected` connections; there is no point of infinite non-wait looping then. + if self.read_maintained_connections().len() >= self.config().min_connected || will_never_get_min_connected { + // Wait for a timeout or a below `min_connected` notification before doing another round of house keeping. + futures::select! { + _ = Timer::sleep(BACKGROUND_TASK_WAIT_TIMEOUT).fuse() => (), + _ = min_connected_notification.wait().fuse() => (), + } + log_errors = true; + } else { + // Never sleeping can result in busy waiting, which is problematic as it might not + // give a chance to other tasks to make progress, especially in single threaded environments. + // Yield the execution to the executor to give a chance to other tasks to run. + // TODO: `yield` keyword is not supported in the current rust version, using a short sleep for now. + Timer::sleep(1.).await; + log_errors = false; + } + } + } + + /// Returns a list of candidate connections that aren't maintained and could be considered for maintaining. + /// + /// Also returns a flag indicating whether covering `min_connected` connections is even possible: not possible when + /// `min_connected` is greater than the number of connections we have. + fn get_candidate_connections(&self) -> (Vec<(Arc, u32)>, bool) { + let all_connections = self.read_connections(); + let maintained_connections = self.read_maintained_connections(); + // The number of connections we need to add as maintained to reach the `min_connected` threshold. + let connections_needed = self.config().min_connected.saturating_sub(maintained_connections.len()); + // The connections that we can consider (all connections - candidate connections). + let all_candidate_connections: Vec<_> = all_connections + .iter() + .filter_map(|(_, conn_ctx)| { + (!maintained_connections.contains_key(&conn_ctx.id)).then(|| (conn_ctx.connection.clone(), conn_ctx.id)) + }) + .collect(); + // The candidate connections from above, but further filtered by whether they are suspended or not. + let non_suspended_candidate_connections: Vec<_> = all_candidate_connections + .iter() + .filter(|(connection, _)| { + all_connections + .get(connection.address()) + .map_or(false, |conn_ctx| now_ms() > conn_ctx.suspended_till()) + }) + .cloned() + .collect(); + // Decide which candidate connections to consider (all or only non-suspended). + if connections_needed > non_suspended_candidate_connections.len() { + if connections_needed > all_candidate_connections.len() { + // Not enough connections to cover the `min_connected` threshold. + // This means we will never be able to maintain `min_connected` active connections. + (all_candidate_connections, true) + } else { + // If we consider all candidate connection (but some are suspended), we can cover the needed connections. + // We will consider the suspended ones since if we don't we will stay below `min_connected` threshold. + (all_candidate_connections, false) + } + } else { + // Non suspended candidates are enough to cover the needed connections. + (non_suspended_candidate_connections, false) + } + } + + /// Establishes the best connections (based on priority) using the candidate connections + /// till we can't establish no more (hit the `max_connected` threshold). + async fn establish_best_connections( + &self, + mut candidate_connections: Vec<(Arc, u32)>, + log_errors: bool, + ) { + let client = unwrap_or_return!(self.get_client()); + // Sort the candidate connections by their priority/ID. + candidate_connections.sort_by_key(|(_, priority)| *priority); + for (connection, connection_id) in candidate_connections { + let address = connection.address().to_string(); + let (maintained_connections_size, lowest_priority_connection_id) = { + let maintained_connections = self.read_maintained_connections(); + let maintained_connections_size = maintained_connections.len(); + let lowest_priority_connection_id = *maintained_connections.keys().next_back().unwrap_or(&u32::MAX); + (maintained_connections_size, lowest_priority_connection_id) + }; + + // We can only try to add the connection if: + // 1- We haven't reached the `max_connected` threshold. + // 2- We have reached the `max_connected` threshold but the connection has a higher priority than the lowest priority connection. + if maintained_connections_size < self.config().max_connected + || connection_id < lowest_priority_connection_id + { + // Now that we know the connection is good to be inserted, try to establish it. + if let Err(e) = connection.establish_connection_loop(client.clone()).await { + if log_errors { + error!("Failed to establish connection to {address} due to error: {e:?}"); + } + // Remove the connection if it's not recoverable. + if !e.is_recoverable() { + self.remove_connection(&address).ok(); + } + continue; + } + self.maintain(connection_id, address); + } else { + // If any of the two conditions on the `if` statement above are not met, there is nothing to do. + // At this point we have already collected `max_connected` connections and also the current connection + // in the candidate list has a lower priority than the lowest priority maintained connection, and the next + // candidate connections as well since they are sorted by priority. + break; + } + } + } +} + +// Abstractions over the accesses of the inner fields of the connection manager. +impl ConnectionManager { + #[inline] + pub fn config(&self) -> &ManagerConfig { &self.0.config } + + #[inline] + fn read_connections(&self) -> RwLockReadGuard> { + self.0.connections.read().unwrap() + } + + #[inline] + fn write_connections(&self) -> RwLockWriteGuard> { + self.0.connections.write().unwrap() + } + + #[inline] + fn get_connection(&self, server_address: &str) -> Option> { + self.read_connections() + .get(server_address) + .map(|connection_ctx| connection_ctx.connection.clone()) + } + + #[inline] + fn read_maintained_connections(&self) -> RwLockReadGuard> { + self.0.maintained_connections.read().unwrap() + } + + #[inline] + fn maintain(&self, id: ID, server_address: String) { + let mut maintained_connections = self.0.maintained_connections.write().unwrap(); + maintained_connections.insert(id, server_address); + // If we have reached the `max_connected` threshold then remove the lowest priority connection. + if maintained_connections.len() > self.config().max_connected { + let lowest_priority_connection_id = *maintained_connections.keys().next_back().unwrap_or(&u32::MAX); + maintained_connections.remove(&lowest_priority_connection_id); + } + } + + #[inline] + fn unmaintain(&self, id: ID) { + // To avoid write locking the maintained connections, just make sure the connection is actually maintained first. + let is_maintained = self.read_maintained_connections().contains_key(&id); + if is_maintained { + // If the connection was maintained, remove it from the maintained connections. + let mut maintained_connections = self.0.maintained_connections.write().unwrap(); + maintained_connections.remove(&id); + // And notify the background task if we fell below the `min_connected` threshold. + if maintained_connections.len() < self.config().min_connected { + self.notify_below_min_connected() + } + } + } + + #[inline] + fn notify_below_min_connected(&self) { self.0.below_min_connected_notifier.notify().ok(); } + + #[inline] + fn extract_below_min_connected_notifiee(&self) -> Option { + self.0.below_min_connected_notifiee.lock().unwrap().take() + } + + #[inline] + fn weak_client(&self) -> &RwLock>> { &self.0.electrum_client } + + #[inline] + fn get_client(&self) -> Option { + self.weak_client() + .read() + .unwrap() + .as_ref() // None here = client was never initialized. + .and_then(|weak| weak.upgrade().map(ElectrumClient)) // None here = client was dropped. + } +} diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/mod.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/mod.rs new file mode 100644 index 0000000000..b301ad3186 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/mod.rs @@ -0,0 +1,4 @@ +mod connection_context; +mod manager; + +pub use manager::ConnectionManager; diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/constants.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/constants.rs new file mode 100644 index 0000000000..f4d1a0efa5 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/constants.rs @@ -0,0 +1,32 @@ +/// The timeout for the electrum server to respond to a request. +pub const ELECTRUM_REQUEST_TIMEOUT: f64 = 20.; +/// The default (can be overridden) maximum timeout to establish a connection with the electrum server. +/// This included connecting to the server and querying the server version. +pub const DEFAULT_CONNECTION_ESTABLISHMENT_TIMEOUT: f64 = 20.; +/// Wait this long before pinging again. +pub const PING_INTERVAL: f64 = 30.; +/// Used to cutoff the server connection after not receiving any response for that long. +/// This only makes sense if we have sent a request to the server. So we need to keep `PING_INTERVAL` +/// lower than this value, otherwise we might disconnect servers that are perfectly responsive but just +/// haven't received any requests from us for a while. +pub const CUTOFF_TIMEOUT: f64 = 60.; +/// Initial server suspension time. +pub const FIRST_SUSPEND_TIME: u64 = 10; +/// The timeout used by the background task of the connection manager to re-check the manager's health. +pub const BACKGROUND_TASK_WAIT_TIMEOUT: f64 = (5 * 60) as f64; +/// Electrum methods that should not be sent without forcing the connection to be established first. +pub const NO_FORCE_CONNECT_METHODS: &[&str] = &[ + // The server should already be connected if we are querying for its version, don't force connect. + "server.version", +]; +/// Electrum methods that should be sent to all connections even after receiving a response from a subset of them. +/// Note that this is only applicable to active/maintained connections. If an electrum request fails by all maintained +/// connections, a fallback using all connections will *NOT* be attempted. +pub const SEND_TO_ALL_METHODS: &[&str] = &[ + // A ping should be sent to all connections even if we got a response from one of them early. + "server.ping", +]; +/// Electrum RPC method for headers subscription. +pub const BLOCKCHAIN_HEADERS_SUB_ID: &str = "blockchain.headers.subscribe"; +/// Electrum RPC method for script/address subscription. +pub const BLOCKCHAIN_SCRIPTHASH_SUB_ID: &str = "blockchain.scripthash.subscribe"; diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/event_handlers.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/event_handlers.rs new file mode 100644 index 0000000000..27bd74b4d9 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/event_handlers.rs @@ -0,0 +1,74 @@ +use super::connection_manager::ConnectionManager; +use super::constants::BLOCKCHAIN_SCRIPTHASH_SUB_ID; + +use crate::utxo::ScripthashNotification; +use crate::RpcTransportEventHandler; +use common::jsonrpc_client::JsonRpcRequest; +use common::log::{error, warn}; + +use futures::channel::mpsc::UnboundedSender; +use serde_json::{self as json, Value as Json}; + +/// An `RpcTransportEventHandler` that forwards `ScripthashNotification`s to trigger balance updates. +/// +/// This handler hooks in `on_incoming_response` and looks for an electrum script hash notification to forward it. +pub struct ElectrumScriptHashNotificationBridge { + pub scripthash_notification_sender: UnboundedSender, +} + +impl RpcTransportEventHandler for ElectrumScriptHashNotificationBridge { + fn debug_info(&self) -> String { "ElectrumScriptHashNotificationBridge".into() } + + fn on_incoming_response(&self, data: &[u8]) { + if let Ok(raw_json) = json::from_slice::(data) { + // Try to parse the notification. A notification is sent as a JSON-RPC request. + if let Ok(notification) = json::from_value::(raw_json) { + // Only care about `BLOCKCHAIN_SCRIPTHASH_SUB_ID` notifications. + if notification.method.as_str() == BLOCKCHAIN_SCRIPTHASH_SUB_ID { + if let Some(scripthash) = notification.params.first().and_then(|s| s.as_str()) { + if let Err(e) = self + .scripthash_notification_sender + .unbounded_send(ScripthashNotification::Triggered(scripthash.to_string())) + { + error!("Failed sending script hash message. {e:?}"); + } + } else { + warn!("Notification must contain the script hash value, got: {notification:?}"); + } + }; + } + } + } + + fn on_connected(&self, _address: &str) -> Result<(), String> { Ok(()) } + + fn on_disconnected(&self, _address: &str) -> Result<(), String> { Ok(()) } + + fn on_outgoing_request(&self, _data: &[u8]) {} +} + +/// An `RpcTransportEventHandler` that notifies the `ConnectionManager` upon connections and disconnections. +/// +/// When a connection is connected or disconnected, this event handler will notify the `ConnectionManager` +/// to handle the the event. +pub struct ElectrumConnectionManagerNotifier { + pub connection_manager: ConnectionManager, +} + +impl RpcTransportEventHandler for ElectrumConnectionManagerNotifier { + fn debug_info(&self) -> String { "ElectrumConnectionManagerNotifier".into() } + + fn on_connected(&self, address: &str) -> Result<(), String> { + self.connection_manager.on_connected(address); + Ok(()) + } + + fn on_disconnected(&self, address: &str) -> Result<(), String> { + self.connection_manager.on_disconnected(address); + Ok(()) + } + + fn on_incoming_response(&self, _data: &[u8]) {} + + fn on_outgoing_request(&self, _data: &[u8]) {} +} diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/mod.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/mod.rs new file mode 100644 index 0000000000..bf78308be2 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/mod.rs @@ -0,0 +1,20 @@ +use sha2::{Digest, Sha256}; + +mod client; +mod connection; +mod connection_manager; +mod constants; +mod event_handlers; +mod rpc_responses; +#[cfg(not(target_arch = "wasm32"))] mod tcp_stream; + +pub use client::{ElectrumClient, ElectrumClientImpl, ElectrumClientSettings}; +pub use connection::ElectrumConnectionSettings; +pub use rpc_responses::*; + +#[inline] +pub fn electrum_script_hash(script: &[u8]) -> Vec { + let mut sha = Sha256::new(); + sha.update(script); + sha.finalize().iter().rev().copied().collect() +} diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/rpc_responses.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/rpc_responses.rs new file mode 100644 index 0000000000..75daac6f35 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/rpc_responses.rs @@ -0,0 +1,168 @@ +use chain::{BlockHeader, BlockHeaderBits, BlockHeaderNonce, Transaction as UtxoTx}; +use mm2_number::{BigDecimal, BigInt}; +use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; +use serialization::serialize; + +#[derive(Debug, Deserialize)] +pub struct ElectrumTxHistoryItem { + pub height: i64, + pub tx_hash: H256Json, + pub fee: Option, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct ElectrumUnspent { + pub height: Option, + pub tx_hash: H256Json, + pub tx_pos: u32, + pub value: u64, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +pub enum ElectrumNonce { + Number(u64), + Hash(H256Json), +} + +#[allow(clippy::from_over_into)] +impl Into for ElectrumNonce { + fn into(self) -> BlockHeaderNonce { + match self { + ElectrumNonce::Number(n) => BlockHeaderNonce::U32(n as u32), + ElectrumNonce::Hash(h) => BlockHeaderNonce::H256(h.into()), + } + } +} + +#[derive(Debug, Deserialize)] +pub struct ElectrumBlockHeadersRes { + pub count: u64, + pub hex: BytesJson, + #[allow(dead_code)] + max: u64, +} + +/// The block header compatible with Electrum 1.2 +#[derive(Clone, Debug, Deserialize)] +pub struct ElectrumBlockHeaderV12 { + pub bits: u64, + pub block_height: u64, + pub merkle_root: H256Json, + pub nonce: ElectrumNonce, + pub prev_block_hash: H256Json, + pub timestamp: u64, + pub version: u64, +} + +impl ElectrumBlockHeaderV12 { + fn as_block_header(&self) -> BlockHeader { + BlockHeader { + version: self.version as u32, + previous_header_hash: self.prev_block_hash.into(), + merkle_root_hash: self.merkle_root.into(), + claim_trie_root: None, + hash_final_sapling_root: None, + time: self.timestamp as u32, + bits: BlockHeaderBits::U32(self.bits as u32), + nonce: self.nonce.clone().into(), + solution: None, + aux_pow: None, + prog_pow: None, + mtp_pow: None, + is_verus: false, + hash_state_root: None, + hash_utxo_root: None, + prevout_stake: None, + vch_block_sig_dlgt: None, + n_height: None, + n_nonce_u64: None, + mix_hash: None, + } + } + + #[inline] + pub fn as_hex(&self) -> String { + let block_header = self.as_block_header(); + let serialized = serialize(&block_header); + hex::encode(serialized) + } + + #[inline] + pub fn hash(&self) -> H256Json { + let block_header = self.as_block_header(); + BlockHeader::hash(&block_header).into() + } +} + +/// The block header compatible with Electrum 1.4 +#[derive(Clone, Debug, Deserialize)] +pub struct ElectrumBlockHeaderV14 { + pub height: u64, + pub hex: BytesJson, +} + +impl ElectrumBlockHeaderV14 { + pub fn hash(&self) -> H256Json { self.hex.clone().into_vec()[..].into() } +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +pub enum ElectrumBlockHeader { + V12(ElectrumBlockHeaderV12), + V14(ElectrumBlockHeaderV14), +} + +impl ElectrumBlockHeader { + pub fn block_height(&self) -> u64 { + match self { + ElectrumBlockHeader::V12(h) => h.block_height, + ElectrumBlockHeader::V14(h) => h.height, + } + } + + pub fn block_hash(&self) -> H256Json { + match self { + ElectrumBlockHeader::V12(h) => h.hash(), + ElectrumBlockHeader::V14(h) => h.hash(), + } + } +} + +/// The merkle branch of a confirmed transaction +#[derive(Clone, Debug, Deserialize)] +pub struct TxMerkleBranch { + pub merkle: Vec, + pub block_height: u64, + pub pos: usize, +} + +#[derive(Clone)] +pub struct ConfirmedTransactionInfo { + pub tx: UtxoTx, + pub header: BlockHeader, + pub index: u64, + pub height: u64, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct ElectrumBalance { + pub(crate) confirmed: i128, + pub(crate) unconfirmed: i128, +} + +impl ElectrumBalance { + #[inline] + pub fn to_big_decimal(&self, decimals: u8) -> BigDecimal { + let balance_sat = BigInt::from(self.confirmed) + BigInt::from(self.unconfirmed); + BigDecimal::from(balance_sat) / BigDecimal::from(10u64.pow(decimals as u32)) + } +} + +#[derive(Debug, Deserialize, Serialize)] +/// Deserializable Electrum protocol version representation for RPC +/// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-methods.html#server.version +pub struct ElectrumProtocolVersion { + pub server_software_version: String, + pub protocol_version: String, +} diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/tcp_stream.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/tcp_stream.rs new file mode 100644 index 0000000000..b50b7f5c85 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/tcp_stream.rs @@ -0,0 +1,105 @@ +use std::io; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::SystemTime; + +use futures::io::Error; +use rustls::client::ServerCertVerified; +use rustls::{Certificate, ClientConfig, OwnedTrustAnchor, RootCertStore, ServerName}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio::net::TcpStream; +use tokio_rustls::client::TlsStream; +use webpki_roots::TLS_SERVER_ROOTS; + +/// The enum wrapping possible variants of underlying Streams +#[allow(clippy::large_enum_variant)] +pub enum ElectrumStream { + Tcp(TcpStream), + Tls(TlsStream), +} + +impl AsRef for ElectrumStream { + fn as_ref(&self) -> &TcpStream { + match self { + ElectrumStream::Tcp(stream) => stream, + ElectrumStream::Tls(stream) => stream.get_ref().0, + } + } +} + +impl AsyncRead for ElectrumStream { + fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { + match self.get_mut() { + ElectrumStream::Tcp(stream) => AsyncRead::poll_read(Pin::new(stream), cx, buf), + ElectrumStream::Tls(stream) => AsyncRead::poll_read(Pin::new(stream), cx, buf), + } + } +} + +impl AsyncWrite for ElectrumStream { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + match self.get_mut() { + ElectrumStream::Tcp(stream) => AsyncWrite::poll_write(Pin::new(stream), cx, buf), + ElectrumStream::Tls(stream) => AsyncWrite::poll_write(Pin::new(stream), cx, buf), + } + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.get_mut() { + ElectrumStream::Tcp(stream) => AsyncWrite::poll_flush(Pin::new(stream), cx), + ElectrumStream::Tls(stream) => AsyncWrite::poll_flush(Pin::new(stream), cx), + } + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.get_mut() { + ElectrumStream::Tcp(stream) => AsyncWrite::poll_shutdown(Pin::new(stream), cx), + ElectrumStream::Tls(stream) => AsyncWrite::poll_shutdown(Pin::new(stream), cx), + } + } +} + +/// Skips the server certificate verification on TLS connection +pub struct NoCertificateVerification {} + +impl rustls::client::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _: &Certificate, + _: &[Certificate], + _: &ServerName, + _: &mut dyn Iterator, + _: &[u8], + _: SystemTime, + ) -> Result { + Ok(rustls::client::ServerCertVerified::assertion()) + } +} + +fn rustls_client_config(unsafe_conf: bool) -> Arc { + let mut cert_store = RootCertStore::empty(); + + cert_store.add_trust_anchors( + TLS_SERVER_ROOTS + .iter() + .map(|ta| OwnedTrustAnchor::from_subject_spki_name_constraints(ta.subject, ta.spki, ta.name_constraints)), + ); + + let mut tls_config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(cert_store) + .with_no_client_auth(); + + if unsafe_conf { + tls_config + .dangerous() + .set_certificate_verifier(Arc::new(NoCertificateVerification {})); + } + Arc::new(tls_config) +} + +lazy_static! { + pub static ref SAFE_TLS_CONFIG: Arc = rustls_client_config(false); + pub static ref UNSAFE_TLS_CONFIG: Arc = rustls_client_config(true); +} diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index c559449c22..cbc7780a34 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -55,6 +55,7 @@ use serde_json::Value as Json; use serialization::{deserialize, serialize, Deserializable, Error as SerError, Reader}; use serialization_derive::Deserializable; use std::convert::TryInto; +use std::num::TryFromIntError; use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; use std::sync::Arc; use utxo_signer::with_key_pair::{p2pkh_spend, p2sh_spend, sign_tx, UtxoSignWithKeyPairError}; @@ -1188,7 +1189,7 @@ impl MarketCoinOps for SlpToken { self.platform_coin.wait_for_confirmations(input) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { utxo_common::wait_for_output_spend( self.clone(), args.tx_bytes, @@ -1197,6 +1198,7 @@ impl MarketCoinOps for SlpToken { args.wait_until, args.check_every, ) + .await } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -1216,63 +1218,56 @@ impl MarketCoinOps for SlpToken { #[async_trait] impl SwapOps for SlpToken { - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { - let coin = self.clone(); - let fee_pubkey = try_tx_fus!(Public::from_slice(fee_addr)); + async fn send_taker_fee( + &self, + fee_addr: &[u8], + dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { + let fee_pubkey = try_tx_s!(Public::from_slice(fee_addr)); let script_pubkey = ScriptBuilder::build_p2pkh(&fee_pubkey.address_hash().into()).into(); - let amount = try_tx_fus!(dex_fee.fee_uamount(self.decimals())); + let amount = try_tx_s!(dex_fee.fee_uamount(self.decimals())); + let slp_out = SlpOutput { amount, script_pubkey }; + let (preimage, recently_spent) = try_tx_s!(self.generate_slp_tx_preimage(vec![slp_out]).await); - let fut = async move { - let slp_out = SlpOutput { amount, script_pubkey }; - let (preimage, recently_spent) = try_tx_s!(coin.generate_slp_tx_preimage(vec![slp_out]).await); - generate_and_send_tx( - &coin, - preimage.available_bch_inputs, - Some(preimage.slp_inputs.into_iter().map(|slp| slp.bch_unspent).collect()), - FeePolicy::SendExact, - recently_spent, - preimage.outputs, - ) - .await - }; - Box::new(fut.boxed().compat().map(|tx| tx.into())) + generate_and_send_tx( + self, + preimage.available_bch_inputs, + Some(preimage.slp_inputs.into_iter().map(|slp| slp.bch_unspent).collect()), + FeePolicy::SendExact, + recently_spent, + preimage.outputs, + ) + .await + .map(|tx| tx.into()) } - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { - let taker_pub = try_tx_fus!(Public::from_slice(maker_payment_args.other_pubkey)); - let amount = try_tx_fus!(sat_from_big_decimal(&maker_payment_args.amount, self.decimals())); + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + let taker_pub = try_tx_s!(Public::from_slice(maker_payment_args.other_pubkey)); + let amount = try_tx_s!(sat_from_big_decimal(&maker_payment_args.amount, self.decimals())); let secret_hash = maker_payment_args.secret_hash.to_owned(); let maker_htlc_keypair = self.derive_htlc_key_pair(maker_payment_args.swap_unique_data); - let time_lock = try_tx_fus!(maker_payment_args.time_lock.try_into()); + let time_lock = try_tx_s!(maker_payment_args.time_lock.try_into()); - let coin = self.clone(); - let fut = async move { - let tx = try_tx_s!( - coin.send_htlc(maker_htlc_keypair.public(), &taker_pub, time_lock, &secret_hash, amount) - .await - ); - Ok(tx.into()) - }; - Box::new(fut.boxed().compat()) + let tx = try_tx_s!( + self.send_htlc(maker_htlc_keypair.public(), &taker_pub, time_lock, &secret_hash, amount) + .await + ); + Ok(tx.into()) } - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { - let maker_pub = try_tx_fus!(Public::from_slice(taker_payment_args.other_pubkey)); - let amount = try_tx_fus!(sat_from_big_decimal(&taker_payment_args.amount, self.decimals())); + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + let maker_pub = try_tx_s!(Public::from_slice(taker_payment_args.other_pubkey)); + let amount = try_tx_s!(sat_from_big_decimal(&taker_payment_args.amount, self.decimals())); let secret_hash = taker_payment_args.secret_hash.to_owned(); let taker_htlc_keypair = self.derive_htlc_key_pair(taker_payment_args.swap_unique_data); - let time_lock = try_tx_fus!(taker_payment_args.time_lock.try_into()); + let time_lock = try_tx_s!(taker_payment_args.time_lock.try_into()); - let coin = self.clone(); - let fut = async move { - let tx = try_tx_s!( - coin.send_htlc(taker_htlc_keypair.public(), &maker_pub, time_lock, &secret_hash, amount) - .await - ); - Ok(tx.into()) - }; - Box::new(fut.boxed().compat()) + self.send_htlc(taker_htlc_keypair.public(), &maker_pub, time_lock, &secret_hash, amount) + .await + .map(|tx| tx.into()) } async fn send_maker_spends_taker_payment( @@ -1343,24 +1338,27 @@ impl SwapOps for SlpToken { Ok(tx.into()) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), - _ => panic!(), + fee_tx => { + return MmError::err(ValidatePaymentError::InternalError(format!( + "Invalid fee tx type. fee tx: {:?}", + fee_tx + ))) + }, }; - let coin = self.clone(); - let expected_sender = validate_fee_args.expected_sender.to_owned(); - let fee_addr = validate_fee_args.fee_addr.to_owned(); - let amount = validate_fee_args.dex_fee.fee_amount(); - let min_block_number = validate_fee_args.min_block_number; - let fut = async move { - coin.validate_dex_fee(tx, &expected_sender, &fee_addr, amount.into(), min_block_number) - .await - .map_err(|e| MmError::new(ValidatePaymentError::WrongPaymentTx(e.into_inner().to_string())))?; - Ok(()) - }; - Box::new(fut.boxed().compat()) + let amount = validate_fee_args.dex_fee.fee_amount(); + self.validate_dex_fee( + tx, + validate_fee_args.expected_sender, + validate_fee_args.fee_addr, + amount.into(), + validate_fee_args.min_block_number, + ) + .await + .map_err(|e| MmError::new(ValidatePaymentError::WrongPaymentTx(e.into_inner().to_string()))) } async fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { @@ -1372,17 +1370,23 @@ impl SwapOps for SlpToken { } #[inline] - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { + let time_lock = if_my_payment_sent_args + .time_lock + .try_into() + .map_err(|e: TryFromIntError| e.to_string())?; utxo_common::check_if_my_payment_sent( self.platform_coin.clone(), - try_fus!(if_my_payment_sent_args.time_lock.try_into()), + time_lock, if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, ) + .compat() + .await } #[inline] @@ -1945,6 +1949,7 @@ mod slp_tests { use crate::utxo::GetUtxoListOps; use crate::{utxo::bch::tbch_coin_for_test, TransactionErr}; use common::block_on; + use common::block_on_f01; use mocktopus::mocking::{MockResult, Mockable}; use std::mem::discriminant; @@ -2206,11 +2211,11 @@ mod slp_tests { ]; let tx_bytes_str = hex::encode(tx_bytes); - let err = fusd.send_raw_tx(&tx_bytes_str).wait().unwrap_err(); + let err = block_on_f01(fusd.send_raw_tx(&tx_bytes_str)).unwrap_err(); println!("{:?}", err); assert!(err.contains("is not valid with reason outputs greater than inputs")); - let err2 = fusd.send_raw_tx_bytes(tx_bytes).wait().unwrap_err(); + let err2 = block_on_f01(fusd.send_raw_tx_bytes(tx_bytes)).unwrap_err(); println!("{:?}", err2); assert!(err2.contains("is not valid with reason outputs greater than inputs")); assert_eq!(err, err2); diff --git a/mm2src/coins/utxo/utxo_balance_events.rs b/mm2src/coins/utxo/utxo_balance_events.rs index 2d97ef5cc9..ec1de7aa40 100644 --- a/mm2src/coins/utxo/utxo_balance_events.rs +++ b/mm2src/coins/utxo/utxo_balance_events.rs @@ -1,20 +1,20 @@ +use super::utxo_standard::UtxoStandardCoin; +use crate::utxo::rpc_clients::UtxoRpcClientEnum; +use crate::{utxo::{output_script, + rpc_clients::electrum_script_hash, + utxo_common::{address_balance, address_to_scripthash}, + ScripthashNotification, UtxoCoinFields}, + CoinWithDerivationMethod, MarketCoinOps, MmCoin}; use async_trait::async_trait; -use common::{executor::{AbortSettings, SpawnAbortable, Timer}, - log, Future01CompatExt}; +use common::{executor::{AbortSettings, SpawnAbortable}, + log}; use futures::channel::oneshot::{self, Receiver, Sender}; use futures_util::StreamExt; use keys::Address; use mm2_core::mm_ctx::MmArc; use mm2_event_stream::{behaviour::{EventBehaviour, EventInitStatus}, ErrorEventName, Event, EventName, EventStreamConfiguration}; -use std::collections::{BTreeMap, HashSet}; - -use super::utxo_standard::UtxoStandardCoin; -use crate::{utxo::{output_script, - rpc_clients::electrum_script_hash, - utxo_common::{address_balance, address_to_scripthash}, - ScripthashNotification, UtxoCoinFields}, - CoinWithDerivationMethod, MarketCoinOps, MmCoin}; +use std::collections::{BTreeMap, HashMap, HashSet}; macro_rules! try_or_continue { ($exp:expr) => { @@ -41,37 +41,29 @@ impl EventBehaviour for UtxoStandardCoin { utxo: &UtxoCoinFields, addresses: HashSet
, ) -> Result, String> { - const LOOP_INTERVAL: f64 = 0.5; - - let mut scripthash_to_address_map: BTreeMap = BTreeMap::new(); - for address in addresses { - let scripthash = address_to_scripthash(&address).map_err(|e| e.to_string())?; - - scripthash_to_address_map.insert(scripthash.clone(), address); - - let mut attempt = 0; - while let Err(e) = utxo - .rpc_client - .blockchain_scripthash_subscribe(scripthash.clone()) - .compat() - .await - { - if attempt == 5 { - return Err(e.to_string()); - } - - log::error!( - "Failed to subscribe {} scripthash ({attempt}/5 attempt). Error: {}", - scripthash, - e.to_string() - ); - - attempt += 1; - Timer::sleep(LOOP_INTERVAL).await; - } + match utxo.rpc_client.clone() { + UtxoRpcClientEnum::Electrum(client) => { + // Collect the scrpithash for every address into a map. + let scripthash_to_address_map = addresses + .into_iter() + .map(|address| { + let scripthash = address_to_scripthash(&address).map_err(|e| e.to_string())?; + Ok((scripthash, address)) + }) + .collect::, String>>()?; + // Add these subscriptions to the connection manager. It will choose whatever connections + // it sees fit to subscribe each of these addresses to. + client + .connection_manager + .add_subscriptions(&scripthash_to_address_map) + .await; + // Convert the hashmap back to btreemap. + Ok(scripthash_to_address_map.into_iter().map(|(k, v)| (k, v)).collect()) + }, + UtxoRpcClientEnum::Native(_) => { + Err("Balance streaming is currently not supported for native client.".to_owned()) + }, } - - Ok(scripthash_to_address_map) } let ctx = match MmArc::from_weak(&self.as_ref().ctx) { @@ -115,24 +107,6 @@ impl EventBehaviour for UtxoStandardCoin { }, }; - continue; - }, - ScripthashNotification::RefreshSubscriptions => { - let my_addresses = try_or_continue!(self.all_addresses().await); - match subscribe_to_addresses(self.as_ref(), my_addresses).await { - Ok(map) => scripthash_to_address_map = map, - Err(e) => { - log::error!("{e}"); - - ctx.stream_channel_controller - .broadcast(Event::new( - format!("{}:{}", Self::error_event_name(), self.ticker()), - json!({ "error": e }).to_string(), - )) - .await; - }, - }; - continue; }, }; diff --git a/mm2src/coins/utxo/utxo_block_header_storage/mod.rs b/mm2src/coins/utxo/utxo_block_header_storage/mod.rs index 89266af2f6..c065016176 100644 --- a/mm2src/coins/utxo/utxo_block_header_storage/mod.rs +++ b/mm2src/coins/utxo/utxo_block_header_storage/mod.rs @@ -27,7 +27,7 @@ impl Debug for BlockHeaderStorage { impl BlockHeaderStorage { #[cfg(all(not(test), not(target_arch = "wasm32")))] pub(crate) fn new_from_ctx(ctx: MmArc, ticker: String) -> Result { - let sqlite_connection = ctx.sqlite_connection.ok_or(BlockHeaderStorageError::Internal( + let sqlite_connection = ctx.sqlite_connection.get().ok_or(BlockHeaderStorageError::Internal( "sqlite_connection is not initialized".to_owned(), ))?; Ok(BlockHeaderStorage { @@ -50,8 +50,11 @@ impl BlockHeaderStorage { use db_common::sqlite::rusqlite::Connection; use std::sync::{Arc, Mutex}; - let conn = Arc::new(Mutex::new(Connection::open_in_memory().unwrap())); - let conn = ctx.sqlite_connection.clone_or(conn); + let conn = ctx + .sqlite_connection + .get() + .cloned() + .unwrap_or_else(|| Arc::new(Mutex::new(Connection::open_in_memory().unwrap()))); Ok(BlockHeaderStorage { inner: Box::new(SqliteBlockHeadersStorage { ticker, conn }), diff --git a/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs index 60b4d75ff0..f8e16a6089 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs @@ -260,14 +260,14 @@ pub(crate) async fn block_header_utxo_loop( ) { macro_rules! remove_server_and_break_if_no_servers_left { ($client:expr, $server_address:expr, $ticker:expr, $sync_status_loop_handle:expr) => { - if let Err(e) = $client.remove_server($server_address).await { + if let Err(e) = $client.remove_server($server_address) { let msg = format!("Error {} on removing server {}!", e, $server_address); // Todo: Permanent error notification should lead to deactivation of coin after applying some fail-safe measures if there are on-going swaps $sync_status_loop_handle.notify_on_permanent_error(msg); break; } - if $client.is_connections_pool_empty().await { + if $client.is_connections_pool_empty() { // Todo: Permanent error notification should lead to deactivation of coin after applying some fail-safe measures if there are on-going swaps let msg = format!("All servers are removed for {}!", $ticker); $sync_status_loop_handle.notify_on_permanent_error(msg); @@ -294,14 +294,14 @@ pub(crate) async fn block_header_utxo_loop( }; let mut args = BlockHeaderUtxoLoopExtraArgs::default(); while let Some(client) = weak.upgrade() { - let client = &ElectrumClient(client); + let client = ElectrumClient(client); let ticker = client.coin_name(); let storage = client.block_headers_storage(); let last_height_in_storage = match storage.get_last_block_height().await { Ok(Some(height)) => height, Ok(None) => { - if let Err(err) = validate_and_store_starting_header(client, ticker, storage, &spv_conf).await { + if let Err(err) = validate_and_store_starting_header(&client, ticker, storage, &spv_conf).await { sync_status_loop_handle.notify_on_permanent_error(err); break; } @@ -372,7 +372,7 @@ pub(crate) async fn block_header_utxo_loop( }; let (block_registry, block_headers) = match try_to_retrieve_headers_until_success( &mut args, - client, + &client, server_address, last_height_in_storage + 1, retrieve_to, @@ -411,7 +411,7 @@ pub(crate) async fn block_header_utxo_loop( } = &err { match resolve_possible_chain_reorg( - client, + &client, server_address, &mut args, last_height_in_storage, diff --git a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs index 0b07d1596c..15a699c2f1 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs @@ -1,41 +1,37 @@ use crate::hd_wallet::{load_hd_accounts_from_storage, HDAccountsMutex, HDWallet, HDWalletCoinStorage, HDWalletStorageError, DEFAULT_GAP_LIMIT}; -use crate::utxo::rpc_clients::{ElectrumClient, ElectrumClientImpl, ElectrumRpcRequest, EstimateFeeMethod, +use crate::utxo::rpc_clients::{ElectrumClient, ElectrumClientSettings, ElectrumConnectionSettings, EstimateFeeMethod, UtxoRpcClientEnum}; use crate::utxo::tx_cache::{UtxoVerboseCacheOps, UtxoVerboseCacheShared}; use crate::utxo::utxo_block_header_storage::BlockHeaderStorage; use crate::utxo::utxo_builder::utxo_conf_builder::{UtxoConfBuilder, UtxoConfError}; -use crate::utxo::{output_script, ElectrumBuilderArgs, ElectrumProtoVerifier, ElectrumProtoVerifierEvent, - RecentlySpentOutPoints, ScripthashNotification, ScripthashNotificationSender, TxFee, UtxoCoinConf, - UtxoCoinFields, UtxoHDWallet, UtxoRpcMode, UtxoSyncStatus, UtxoSyncStatusLoopHandle, - UTXO_DUST_AMOUNT}; +use crate::utxo::{output_script, ElectrumBuilderArgs, RecentlySpentOutPoints, ScripthashNotification, + ScripthashNotificationSender, TxFee, UtxoCoinConf, UtxoCoinFields, UtxoHDWallet, UtxoRpcMode, + UtxoSyncStatus, UtxoSyncStatusLoopHandle, UTXO_DUST_AMOUNT}; use crate::{BlockchainNetwork, CoinTransportMetrics, DerivationMethod, HistorySyncState, IguanaPrivKey, - PrivKeyBuildPolicy, PrivKeyPolicy, PrivKeyPolicyNotAllowed, RpcClientType, UtxoActivationParams}; + PrivKeyBuildPolicy, PrivKeyPolicy, PrivKeyPolicyNotAllowed, RpcClientType, + SharableRpcTransportEventHandler, UtxoActivationParams}; use async_trait::async_trait; use chain::TxHashAlgo; -use common::custom_futures::repeatable::{Ready, Retry}; -use common::executor::{abortable_queue::AbortableQueue, AbortSettings, AbortableSystem, AbortedError, SpawnAbortable, - Timer}; -use common::log::{error, info, LogOnError}; -use common::{now_sec, small_rng}; +use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError}; +use common::now_sec; use crypto::{Bip32DerPathError, CryptoCtx, CryptoCtxError, GlobalHDAccountArc, HwWalletType, StandardHDPathError}; use derive_more::Display; -use futures::channel::mpsc::{channel, unbounded, Receiver as AsyncReceiver, UnboundedReceiver, UnboundedSender}; +use futures::channel::mpsc::{channel, Receiver as AsyncReceiver, UnboundedReceiver, UnboundedSender}; use futures::compat::Future01CompatExt; use futures::lock::Mutex as AsyncMutex; -use futures::StreamExt; use keys::bytes::Bytes; pub use keys::{Address, AddressBuilder, AddressFormat as UtxoAddressFormat, AddressHashEnum, AddressScriptType, KeyPair, Private, Public, Secret}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use primitives::hash::H160; -use rand::seq::SliceRandom; use serde_json::{self as json, Value as Json}; use spv_validation::conf::SPVConf; use spv_validation::helpers_validation::SPVError; use spv_validation::storage::{BlockHeaderStorageError, BlockHeaderStorageOps}; -use std::sync::{Arc, Mutex, Weak}; +use std::sync::Arc; +use std::sync::Mutex; cfg_native! { use crate::utxo::coin_daemon_data_dir; @@ -60,16 +56,6 @@ pub enum UtxoCoinBuildError { ErrorDetectingFeeMethod(String), ErrorDetectingDecimals(String), InvalidBlockchainNetwork(String), - #[display( - fmt = "Failed to connect to at least 1 of {:?} in {} seconds.", - electrum_servers, - seconds - )] - FailedToConnectToElectrums { - electrum_servers: Vec, - seconds: u64, - }, - ElectrumProtocolVersionCheckError(String), #[display(fmt = "Can not detect the user home directory")] CantDetectUserHome, #[display(fmt = "Private key policy is not allowed: {}", _0)] @@ -82,8 +68,6 @@ pub enum UtxoCoinBuildError { )] CoinDoesntSupportTrezor, BlockHeaderStorageError(BlockHeaderStorageError), - #[display(fmt = "Error {} on getting the height of the latest block from rpc!", _0)] - CantGetBlockCount(String), #[display(fmt = "Internal error: {}", _0)] Internal(String), #[display(fmt = "SPV params verificaiton failed. Error: {_0}")] @@ -252,10 +236,7 @@ fn get_scripthash_notification_handlers( Arc>>, )> { if ctx.event_stream_configuration.is_some() { - let (sender, receiver): ( - UnboundedSender, - UnboundedReceiver, - ) = futures::channel::mpsc::unbounded(); + let (sender, receiver) = futures::channel::mpsc::unbounded(); Some((sender, Arc::new(AsyncMutex::new(receiver)))) } else { None @@ -565,12 +546,17 @@ pub trait UtxoCoinBuilderCommonOps { Ok(UtxoRpcClientEnum::Native(native)) } }, - UtxoRpcMode::Electrum { servers } => { + UtxoRpcMode::Electrum { + servers, + min_connected, + max_connected, + } => { let electrum = self .electrum_client( abortable_system, ElectrumBuilderArgs::default(), servers, + (min_connected, max_connected), scripthash_notification_sender, ) .await?; @@ -585,21 +571,19 @@ pub trait UtxoCoinBuilderCommonOps { &self, abortable_system: AbortableQueue, args: ElectrumBuilderArgs, - mut servers: Vec, + servers: Vec, + (min_connected, max_connected): (Option, Option), scripthash_notification_sender: ScripthashNotificationSender, ) -> UtxoCoinBuildResult { - let (on_event_tx, on_event_rx) = unbounded(); - let ticker = self.ticker().to_owned(); + let coin_ticker = self.ticker().to_owned(); let ctx = self.ctx(); - let mut event_handlers = vec![]; + let mut event_handlers: Vec> = vec![]; if args.collect_metrics { - event_handlers.push( - CoinTransportMetrics::new(ctx.metrics.weak(), ticker.clone(), RpcClientType::Electrum).into_shared(), - ); - } - - if args.negotiate_version { - event_handlers.push(ElectrumProtoVerifier { on_event_tx }.into_shared()); + event_handlers.push(Box::new(CoinTransportMetrics::new( + ctx.metrics.weak(), + coin_ticker.clone(), + RpcClientType::Electrum, + ))); } let storage_ticker = self.ticker().replace('-', "_"); @@ -609,56 +593,27 @@ pub trait UtxoCoinBuilderCommonOps { block_headers_storage.init().await?; } - let mut rng = small_rng(); - servers.as_mut_slice().shuffle(&mut rng); + let gui = ctx.gui().unwrap_or("UNKNOWN").to_string(); + let mm_version = ctx.mm_version().to_string(); + let (min_connected, max_connected) = (min_connected.unwrap_or(1), max_connected.unwrap_or(servers.len())); + let client_settings = ElectrumClientSettings { + client_name: format!("{} GUI/MM2 {}", gui, mm_version), + servers: servers.clone(), + coin_ticker, + spawn_ping: args.spawn_ping, + negotiate_version: args.negotiate_version, + min_connected, + max_connected, + }; - let client = ElectrumClientImpl::new( - ticker, + ElectrumClient::try_new( + client_settings, event_handlers, block_headers_storage, abortable_system, - args.negotiate_version, scripthash_notification_sender, - ); - for server in servers.iter() { - match client.add_server(server).await { - Ok(_) => (), - Err(e) => error!("Error {:?} connecting to {:?}. Address won't be used", e, server), - }; - } - - let mut attempts = 0i32; - while !client.is_connected().await { - if attempts >= 10 { - return MmError::err(UtxoCoinBuildError::FailedToConnectToElectrums { - electrum_servers: servers.clone(), - seconds: 5, - }); - } - - Timer::sleep(0.5).await; - attempts += 1; - } - - let client = Arc::new(client); - - let spawner = client.spawner(); - if args.negotiate_version { - let weak_client = Arc::downgrade(&client); - let client_name = format!("{} GUI/MM2 {}", ctx.gui().unwrap_or("UNKNOWN"), ctx.mm_version()); - spawn_electrum_version_loop(&spawner, weak_client, on_event_rx, client_name); - - wait_for_protocol_version_checked(&client) - .await - .map_to_mm(UtxoCoinBuildError::ElectrumProtocolVersionCheckError)?; - } - - if args.spawn_ping { - let weak_client = Arc::downgrade(&client); - spawn_electrum_ping_loop(&spawner, weak_client, servers); - } - - Ok(ElectrumClient(client)) + ) + .map_to_mm(UtxoCoinBuildError::Internal) } #[cfg(not(target_arch = "wasm32"))] @@ -756,6 +711,7 @@ pub trait UtxoCoinBuilderCommonOps { #[cfg(target_arch = "wasm32")] fn tx_cache(&self) -> UtxoVerboseCacheShared { + #[allow(clippy::default_constructed_unit_structs)] // This is a false-possitive bug from clippy crate::utxo::tx_cache::wasm_tx_cache::WasmVerboseCache::default().into_shared() } @@ -868,140 +824,3 @@ fn read_native_mode_conf( ))); Ok((rpc_port, rpc_user.clone(), rpc_password.clone())) } - -/// Ping the electrum servers every 30 seconds to prevent them from disconnecting us. -/// According to docs server can do it if there are no messages in ~10 minutes. -/// https://electrumx.readthedocs.io/en/latest/protocol-methods.html?highlight=keep#server-ping -/// Weak reference will allow to stop the thread if client is dropped. -fn spawn_electrum_ping_loop( - spawner: &Spawner, - weak_client: Weak, - servers: Vec, -) { - let msg_on_stopped = format!("Electrum servers {servers:?} ping loop stopped"); - let fut = async move { - loop { - if let Some(client) = weak_client.upgrade() { - if let Err(e) = ElectrumClient(client).server_ping().compat().await { - error!("Electrum servers {:?} ping error: {}", servers, e); - } - } else { - break; - } - Timer::sleep(30.).await - } - }; - - let settings = AbortSettings::info_on_any_stop(msg_on_stopped); - spawner.spawn_with_settings(fut, settings); -} - -/// Follow the `on_connect_rx` stream and verify the protocol version of each connected electrum server. -/// https://electrumx.readthedocs.io/en/latest/protocol-methods.html?highlight=keep#server-version -/// Weak reference will allow to stop the thread if client is dropped. -fn spawn_electrum_version_loop( - spawner: &Spawner, - weak_client: Weak, - mut on_event_rx: UnboundedReceiver, - client_name: String, -) { - let fut = async move { - while let Some(event) = on_event_rx.next().await { - match event { - ElectrumProtoVerifierEvent::Connected(electrum_addr) => { - check_electrum_server_version(weak_client.clone(), client_name.clone(), electrum_addr).await - }, - ElectrumProtoVerifierEvent::Disconnected(electrum_addr) => { - if let Some(client) = weak_client.upgrade() { - client.reset_protocol_version(&electrum_addr).await.error_log(); - } - }, - } - } - }; - let settings = AbortSettings::info_on_any_stop("Electrum server.version loop stopped".to_string()); - spawner.spawn_with_settings(fut, settings); -} - -async fn check_electrum_server_version( - weak_client: Weak, - client_name: String, - electrum_addr: String, -) { - // client.remove_server() is called too often - async fn remove_server(client: ElectrumClient, electrum_addr: &str) { - if let Err(e) = client.remove_server(electrum_addr).await { - error!("Error on remove server: {}", e); - } - } - - if let Some(c) = weak_client.upgrade() { - let client = ElectrumClient(c); - let available_protocols = client.protocol_version(); - let version = match client - .server_version(&electrum_addr, &client_name, available_protocols) - .compat() - .await - { - Ok(version) => version, - Err(e) => { - error!("Electrum {} server.version error: {:?}", electrum_addr, e); - if !e.error.is_transport() { - remove_server(client, &electrum_addr).await; - }; - return; - }, - }; - - // check if the version is allowed - let actual_version = match version.protocol_version.parse::() { - Ok(v) => v, - Err(e) => { - error!("Error on parse protocol_version: {:?}", e); - remove_server(client, &electrum_addr).await; - return; - }, - }; - - if !available_protocols.contains(&actual_version) { - error!( - "Received unsupported protocol version {:?} from {:?}. Remove the connection", - actual_version, electrum_addr - ); - remove_server(client, &electrum_addr).await; - return; - } - - match client.set_protocol_version(&electrum_addr, actual_version).await { - Ok(()) => info!( - "Use protocol version {:?} for Electrum {:?}", - actual_version, electrum_addr - ), - Err(e) => error!("Error on set protocol_version: {}", e), - }; - } -} - -/// Wait until the protocol version of at least one client's Electrum is checked. -async fn wait_for_protocol_version_checked(client: &ElectrumClientImpl) -> Result<(), String> { - repeatable!(async { - if client.count_connections().await == 0 { - // All of the connections were removed because of server.version checking - return Ready(ERR!( - "There are no Electrums with the required protocol version {:?}", - client.protocol_version() - )); - } - - if client.is_protocol_version_checked().await { - return Ready(Ok(())); - } - Retry(()) - }) - .repeat_every_secs(0.5) - .attempts(10) - .await - .map_err(|_exceed| ERRL!("Failed protocol version verifying of at least 1 of Electrums in 5 seconds.")) - // Flatten `Result< Result<(), String>, String >` - .flatten() -} diff --git a/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs index befbae70f9..5ae7fcb405 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs @@ -210,10 +210,9 @@ impl<'a> UtxoConfBuilder<'a> { fn overwintered(&self) -> bool { self.conf["overwintered"].as_u64().unwrap_or(0) == 1 } fn tx_fee_volatility_percent(&self) -> f64 { - match self.conf["txfee_volatility_percent"].as_f64() { - Some(volatility) => volatility, - None => DEFAULT_DYNAMIC_FEE_VOLATILITY_PERCENT, - } + self.conf["txfee_volatility_percent"] + .as_f64() + .unwrap_or(DEFAULT_DYNAMIC_FEE_VOLATILITY_PERCENT) } fn version_group_id(&self, tx_version: i32, overwintered: bool) -> UtxoConfResult { diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index be54d53fe1..70c8522b58 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -15,7 +15,7 @@ use crate::watcher_common::validate_watcher_reward; use crate::{scan_for_new_addresses_impl, CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, DexFee, GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, RawTransactionError, RawTransactionRequest, RawTransactionRes, RawTransactionResult, - RefundFundingSecretArgs, RefundMakerPaymentArgs, RefundPaymentArgs, RewardTarget, + RefundFundingSecretArgs, RefundMakerPaymentSecretArgs, RefundPaymentArgs, RewardTarget, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionEnum, SignRawTransactionRequest, SignUtxoTransactionParams, SignatureError, SignatureResult, SpendMakerPaymentArgs, SpendPaymentArgs, SwapOps, @@ -2085,7 +2085,7 @@ pub fn watcher_validate_taker_fee( if tx_confirmed_before_block { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "{}: Fee tx {:?} confirmed before min_block {}", - EARLY_CONFIRMATION_ERR_LOG, taker_fee_tx, min_block_number + EARLY_CONFIRMATION_ERR_LOG, tx_from_rpc, min_block_number ))); } @@ -2871,13 +2871,12 @@ pub async fn wait_for_output_spend_impl( wait_until: u64, check_every: f64, ) -> MmResult { + let script_pubkey = &tx + .outputs + .get(output_index) + .or_mm_err(|| WaitForOutputSpendErr::NoOutputWithIndex(output_index))? + .script_pubkey; loop { - let script_pubkey = &tx - .outputs - .get(output_index) - .or_mm_err(|| WaitForOutputSpendErr::NoOutputWithIndex(output_index))? - .script_pubkey; - match coin .rpc_client .find_output_spend( @@ -2905,24 +2904,21 @@ pub async fn wait_for_output_spend_impl( } } -pub fn wait_for_output_spend + Send + Sync + 'static>( +pub async fn wait_for_output_spend + Send + Sync + 'static>( coin: T, tx_bytes: &[u8], output_index: usize, from_block: u64, wait_until: u64, check_every: f64, -) -> TransactionFut { - let mut tx: UtxoTx = try_tx_fus!(deserialize(tx_bytes).map_err(|e| ERRL!("{:?}", e))); +) -> TransactionResult { + let mut tx: UtxoTx = try_tx_s!(deserialize(tx_bytes).map_err(|e| ERRL!("{:?}", e))); tx.tx_hash_algo = coin.as_ref().tx_hash_algo; - let fut = async move { - wait_for_output_spend_impl(coin.as_ref(), &tx, output_index, from_block, wait_until, check_every) - .await - .map(|tx| tx.into()) - .map_err(|e| TransactionErr::Plain(format!("{:?}", e))) - }; - Box::new(fut.boxed().compat()) + wait_for_output_spend_impl(coin.as_ref(), &tx, output_index, from_block, wait_until, check_every) + .await + .map(|tx| tx.into()) + .map_err(|e| TransactionErr::Plain(format!("{:?}", e))) } pub fn tx_enum_from_bytes(coin: &UtxoCoinFields, bytes: &[u8]) -> Result> { @@ -4795,7 +4791,7 @@ where outputs, } = try_tx_s!(generate_swap_payment_outputs( &coin, - try_tx_s!(args.time_lock.try_into()), + try_tx_s!(args.funding_time_lock.try_into()), taker_htlc_key_pair.public_slice(), args.maker_pub, total_amount, @@ -4831,7 +4827,7 @@ where .push_opcode(Opcode::OP_0) .push_opcode(Opcode::OP_0) .into_script(); - let time_lock = try_tx_s!(args.time_lock.try_into()); + let time_lock = try_tx_s!(args.funding_time_lock.try_into()); let redeem_script = swap_proto_v2_scripts::taker_funding_script( time_lock, @@ -4886,14 +4882,14 @@ where let expected_amount_sat = sat_from_big_decimal(&total_expected_amount, coin.as_ref().decimals)?; let time_lock = args - .time_lock + .funding_time_lock .try_into() - .map_to_mm(|e: TryFromIntError| ValidateSwapV2TxError::LocktimeOverflow(e.to_string()))?; + .map_to_mm(|e: TryFromIntError| ValidateSwapV2TxError::Overflow(e.to_string()))?; let redeem_script = swap_proto_v2_scripts::taker_funding_script( time_lock, args.taker_secret_hash, - args.other_pub, + args.taker_pub, maker_htlc_key_pair.public(), ); let expected_output = TransactionOutput { @@ -4998,11 +4994,7 @@ where valid_addresses.insert(valid_address); } if let UtxoRpcClientEnum::Electrum(electrum_client) = &coin.as_ref().rpc_client { - if let Some(sender) = &electrum_client.scripthash_notification_sender { - sender - .unbounded_send(ScripthashNotification::SubscribeToAddresses(valid_addresses)) - .map_err(|e| ERRL!("Failed sending scripthash message. {}", e))?; - } + electrum_client.subscribe_addresses(valid_addresses)?; }; Ok(()) @@ -5069,7 +5061,7 @@ pub async fn spend_maker_payment_v2( /// Common implementation of maker payment v2 reclaim for UTXO coins using immediate refund path with secret reveal. pub async fn refund_maker_payment_v2_secret( coin: T, - args: RefundMakerPaymentArgs<'_, T>, + args: RefundMakerPaymentSecretArgs<'_, T>, ) -> Result where T: UtxoCommonOps + SwapOps, diff --git a/mm2src/coins/utxo/utxo_common_tests.rs b/mm2src/coins/utxo/utxo_common_tests.rs index 851f4a0644..03fea53699 100644 --- a/mm2src/coins/utxo/utxo_common_tests.rs +++ b/mm2src/coins/utxo/utxo_common_tests.rs @@ -141,7 +141,7 @@ pub(super) fn utxo_coin_fields_for_test( priv_key_policy, derivation_method, history_sync_state: Mutex::new(HistorySyncState::NotEnabled), - tx_cache: DummyVerboseCache::default().into_shared(), + tx_cache: DummyVerboseCache.into_shared(), recently_spent_outpoints: AsyncMutex::new(RecentlySpentOutPoints::new(my_script_pubkey)), tx_hash_algo: TxHashAlgo::DSHA256, check_utxo_maturity: false, diff --git a/mm2src/coins/utxo/utxo_hd_wallet.rs b/mm2src/coins/utxo/utxo_hd_wallet.rs index a646041f2d..5297157e33 100644 --- a/mm2src/coins/utxo/utxo_hd_wallet.rs +++ b/mm2src/coins/utxo/utxo_hd_wallet.rs @@ -2,11 +2,11 @@ use crate::hd_wallet::{HDAccount, HDAccountMut, HDAccountOps, HDAccountsMap, HDA HDAddress, HDWallet, HDWalletCoinStorage, HDWalletOps, HDWalletStorageOps, WithdrawSenderAddress}; use async_trait::async_trait; -use crypto::{Bip44Chain, HDPathToCoin}; +use crypto::{Bip44Chain, HDPathToCoin, Secp256k1ExtendedPublicKey}; use keys::{Address, AddressFormat as UtxoAddressFormat, Public}; pub type UtxoHDAddress = HDAddress; -pub type UtxoHDAccount = HDAccount; +pub type UtxoHDAccount = HDAccount; pub type UtxoWithdrawSender = WithdrawSenderAddress; /// A struct to encapsulate the types needed for a UTXO HD wallet. diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 03c889d790..f5a02f5095 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -1,10 +1,10 @@ use super::utxo_common::utxo_prepare_addresses_for_balance_stream_if_enabled; use super::*; use crate::coin_balance::{self, EnableCoinBalanceError, EnabledCoinBalanceParams, HDAccountBalance, HDAddressBalance, - HDBalanceAddress, HDWalletBalance, HDWalletBalanceOps}; + HDWalletBalance, HDWalletBalanceOps}; use crate::coin_errors::{MyAddressError, ValidatePaymentResult}; -use crate::hd_wallet::{ExtractExtendedPubkey, HDCoinAddress, HDCoinHDAccount, HDCoinHDAddress, HDCoinWithdrawOps, - HDConfirmAddress, HDExtractPubkeyError, HDXPubExtractor, TrezorCoinError, WithdrawSenderAddress}; +use crate::hd_wallet::{ExtractExtendedPubkey, HDCoinAddress, HDCoinWithdrawOps, HDConfirmAddress, + HDExtractPubkeyError, HDXPubExtractor, TrezorCoinError, WithdrawSenderAddress}; use crate::my_tx_history_v2::{CoinWithTxHistoryV2, MyTxHistoryErrorV2, MyTxHistoryTarget, TxHistoryStorage}; use crate::rpc_command::account_balance::{self, AccountBalanceParams, AccountBalanceRpcOps, HDAccountBalanceResponse}; use crate::rpc_command::get_new_address::{self, GetNewAddressParams, GetNewAddressResponse, GetNewAddressRpcError, @@ -19,15 +19,17 @@ use crate::rpc_command::init_withdraw::{InitWithdrawCoin, WithdrawTaskHandleShar use crate::tx_history_storage::{GetTxHistoryFilters, WalletId}; use crate::utxo::rpc_clients::BlockHashOrHeight; use crate::utxo::utxo_builder::{UtxoArcBuilder, UtxoCoinBuilder}; +use crate::utxo::utxo_hd_wallet::{UtxoHDAccount, UtxoHDAddress}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; -use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, CoinWithPrivKeyPolicy, - ConfirmPaymentInput, DexFee, FundingTxSpend, GenPreimageResult, GenTakerFundingSpendArgs, - GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, IguanaBalanceOps, IguanaPrivKey, MakerCoinSwapOpsV2, - MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, - PaymentInstructionsErr, PrivKeyBuildPolicy, RawTransactionRequest, RawTransactionResult, RefundError, - RefundFundingSecretArgs, RefundMakerPaymentArgs, RefundPaymentArgs, RefundResult, - SearchForFundingSpendErr, SearchForSwapTxSpendInput, SendMakerPaymentArgs, +use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinBalanceMap, CoinWithDerivationMethod, + CoinWithPrivKeyPolicy, CommonSwapOpsV2, ConfirmPaymentInput, DexFee, FundingTxSpend, GenPreimageResult, + GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, IguanaBalanceOps, + IguanaPrivKey, MakerCoinSwapOpsV2, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, + PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, + RawTransactionRequest, RawTransactionResult, RefundError, RefundFundingSecretArgs, + RefundMakerPaymentSecretArgs, RefundMakerPaymentTimelockArgs, RefundPaymentArgs, RefundResult, + RefundTakerPaymentArgs, SearchForFundingSpendErr, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionRequest, SignatureResult, SpendMakerPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageValue, TransactionFut, TransactionResult, @@ -35,7 +37,7 @@ use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDeriva ValidateMakerPaymentArgs, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateSwapV2TxResult, ValidateTakerFundingArgs, ValidateTakerFundingSpendPreimageResult, ValidateTakerPaymentSpendPreimageResult, - ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WaitForTakerPaymentSpendError, + ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WaitForPaymentSpendError, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; use common::executor::{AbortableSystem, AbortedError}; @@ -301,18 +303,30 @@ impl UtxoStandardOps for UtxoStandardCoin { #[async_trait] impl SwapOps for UtxoStandardCoin { #[inline] - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { + async fn send_taker_fee( + &self, + fee_addr: &[u8], + dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { utxo_common::send_taker_fee(self.clone(), fee_addr, dex_fee) + .compat() + .await } #[inline] - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { utxo_common::send_maker_payment(self.clone(), maker_payment_args) + .compat() + .await } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { utxo_common::send_taker_payment(self.clone(), taker_payment_args) + .compat() + .await } #[inline] @@ -341,10 +355,15 @@ impl SwapOps for UtxoStandardCoin { utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args).await } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), - _ => panic!(), + fee_tx => { + return MmError::err(ValidatePaymentError::InternalError(format!( + "Invalid fee tx type. fee tx: {:?}", + fee_tx + ))) + }, }; utxo_common::validate_fee( self.clone(), @@ -355,6 +374,8 @@ impl SwapOps for UtxoStandardCoin { validate_fee_args.min_block_number, validate_fee_args.fee_addr, ) + .compat() + .await } #[inline] @@ -368,17 +389,23 @@ impl SwapOps for UtxoStandardCoin { } #[inline] - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { + let time_lock = if_my_payment_sent_args + .time_lock + .try_into() + .map_err(|e: TryFromIntError| e.to_string())?; utxo_common::check_if_my_payment_sent( self.clone(), - try_fus!(if_my_payment_sent_args.time_lock.try_into()), + time_lock, if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, ) + .compat() + .await } #[inline] @@ -413,13 +440,10 @@ impl SwapOps for UtxoStandardCoin { } #[inline] - fn can_refund_htlc(&self, locktime: u64) -> Box + Send + '_> { - Box::new( - utxo_common::can_refund_htlc(self, locktime) - .boxed() - .map_err(|e| ERRL!("{}", e)) - .compat(), - ) + async fn can_refund_htlc(&self, locktime: u64) -> Result { + utxo_common::can_refund_htlc(self, locktime) + .await + .map_err(|e| ERRL!("{}", e)) } fn is_auto_refundable(&self) -> bool { false } @@ -636,13 +660,25 @@ impl MakerCoinSwapOpsV2 for UtxoStandardCoin { .await } - async fn refund_maker_payment_v2_timelock(&self, args: RefundPaymentArgs<'_>) -> Result { + async fn refund_maker_payment_v2_timelock( + &self, + args: RefundMakerPaymentTimelockArgs<'_>, + ) -> Result { + let args = RefundPaymentArgs { + payment_tx: args.payment_tx, + time_lock: args.time_lock, + other_pubkey: args.taker_pub, + tx_type_with_secret_hash: args.tx_type_with_secret_hash, + swap_contract_address: &None, + swap_unique_data: args.swap_unique_data, + watcher_reward: args.watcher_reward, + }; utxo_common::refund_htlc_payment(self.clone(), args).await } async fn refund_maker_payment_v2_secret( &self, - args: RefundMakerPaymentArgs<'_, Self>, + args: RefundMakerPaymentSecretArgs<'_, Self>, ) -> Result { utxo_common::refund_maker_payment_v2_secret(self.clone(), args).await } @@ -662,7 +698,19 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { utxo_common::validate_taker_funding(self, args).await } - async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> Result { + async fn refund_taker_funding_timelock( + &self, + args: RefundTakerPaymentArgs<'_>, + ) -> Result { + let args = RefundPaymentArgs { + payment_tx: args.payment_tx, + time_lock: args.time_lock, + other_pubkey: args.maker_pub, + tx_type_with_secret_hash: args.tx_type_with_secret_hash, + swap_contract_address: &None, + swap_unique_data: args.swap_unique_data, + watcher_reward: args.watcher_reward, + }; utxo_common::refund_htlc_payment(self.clone(), args).await } @@ -774,7 +822,19 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { utxo_common::sign_and_send_taker_funding_spend(self, preimage, args, &htlc_keypair).await } - async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> Result { + async fn refund_combined_taker_payment( + &self, + args: RefundTakerPaymentArgs<'_>, + ) -> Result { + let args = RefundPaymentArgs { + payment_tx: args.payment_tx, + time_lock: args.time_lock, + other_pubkey: args.maker_pub, + tx_type_with_secret_hash: args.tx_type_with_secret_hash, + swap_contract_address: &None, + swap_unique_data: args.swap_unique_data, + watcher_reward: args.watcher_reward, + }; utxo_common::refund_htlc_payment(self.clone(), args).await } @@ -811,7 +871,7 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { taker_payment: &Self::Tx, from_block: u64, wait_until: u64, - ) -> MmResult { + ) -> MmResult { let res = utxo_common::wait_for_output_spend_impl( self.as_ref(), taker_payment, @@ -823,10 +883,17 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { .await?; Ok(res) } +} +impl CommonSwapOpsV2 for UtxoStandardCoin { fn derive_htlc_pubkey_v2(&self, swap_unique_data: &[u8]) -> Self::Pubkey { *self.derive_htlc_key_pair(swap_unique_data).public() } + + #[inline(always)] + fn derive_htlc_pubkey_v2_bytes(&self, swap_unique_data: &[u8]) -> Vec { + self.derive_htlc_pubkey_v2(swap_unique_data).to_bytes() + } } #[async_trait] @@ -877,7 +944,7 @@ impl MarketCoinOps for UtxoStandardCoin { utxo_common::wait_for_confirmations(&self.utxo_arc, input) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { utxo_common::wait_for_output_spend( self.clone(), args.tx_bytes, @@ -886,6 +953,7 @@ impl MarketCoinOps for UtxoStandardCoin { args.wait_until, args.check_every, ) + .await } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -1067,9 +1135,12 @@ impl CoinWithDerivationMethod for UtxoStandardCoin { #[async_trait] impl IguanaBalanceOps for UtxoStandardCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; - async fn iguana_balances(&self) -> BalanceResult { self.my_balance().compat().await } + async fn iguana_balances(&self) -> BalanceResult { + let balance = self.my_balance().compat().await?; + Ok(HashMap::from([(self.ticker().to_string(), balance)])) + } } #[async_trait] @@ -1096,7 +1167,7 @@ impl HDWalletCoinOps for UtxoStandardCoin { &self, extended_pubkey: &Secp256k1ExtendedPublicKey, derivation_path: DerivationPath, - ) -> HDCoinHDAddress { + ) -> UtxoHDAddress { utxo_common::address_from_extended_pubkey(self, extended_pubkey, derivation_path) } @@ -1107,7 +1178,7 @@ impl HDCoinWithdrawOps for UtxoStandardCoin {} #[async_trait] impl GetNewAddressRpcOps for UtxoStandardCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn get_new_address_rpc_without_conf( &self, @@ -1131,7 +1202,7 @@ impl GetNewAddressRpcOps for UtxoStandardCoin { #[async_trait] impl HDWalletBalanceOps for UtxoStandardCoin { type HDAddressScanner = UtxoAddressScanner; - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn produce_hd_address_scanner(&self) -> BalanceResult { utxo_common::produce_hd_address_scanner(self).await @@ -1153,7 +1224,7 @@ impl HDWalletBalanceOps for UtxoStandardCoin { async fn scan_for_new_addresses( &self, hd_wallet: &Self::HDWallet, - hd_account: &mut HDCoinHDAccount, + hd_account: &mut UtxoHDAccount, address_scanner: &Self::HDAddressScanner, gap_limit: u32, ) -> BalanceResult>> { @@ -1162,20 +1233,27 @@ impl HDWalletBalanceOps for UtxoStandardCoin { async fn all_known_addresses_balances( &self, - hd_account: &HDCoinHDAccount, + hd_account: &UtxoHDAccount, ) -> BalanceResult>> { utxo_common::all_known_addresses_balances(self, hd_account).await } - async fn known_address_balance(&self, address: &HDBalanceAddress) -> BalanceResult { - utxo_common::address_balance(self, address).await + async fn known_address_balance(&self, address: &Address) -> BalanceResult { + let balance = utxo_common::address_balance(self, address).await?; + Ok(HashMap::from([(self.ticker().to_string(), balance)])) } async fn known_addresses_balances( &self, - addresses: Vec>, - ) -> BalanceResult, Self::BalanceObject)>> { - utxo_common::addresses_balances(self, addresses).await + addresses: Vec
, + ) -> BalanceResult> { + let ticker = self.ticker().to_string(); + let balances = utxo_common::addresses_balances(self, addresses).await?; + + balances + .into_iter() + .map(|(address, balance)| Ok((address, HashMap::from([(ticker.clone(), balance)])))) + .collect() } async fn prepare_addresses_for_balance_stream_if_enabled( @@ -1188,7 +1266,7 @@ impl HDWalletBalanceOps for UtxoStandardCoin { #[async_trait] impl AccountBalanceRpcOps for UtxoStandardCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn account_balance_rpc( &self, @@ -1200,7 +1278,7 @@ impl AccountBalanceRpcOps for UtxoStandardCoin { #[async_trait] impl InitAccountBalanceRpcOps for UtxoStandardCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn init_account_balance_rpc( &self, @@ -1212,7 +1290,7 @@ impl InitAccountBalanceRpcOps for UtxoStandardCoin { #[async_trait] impl InitScanAddressesRpcOps for UtxoStandardCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn init_scan_for_new_addresses_rpc( &self, @@ -1224,7 +1302,7 @@ impl InitScanAddressesRpcOps for UtxoStandardCoin { #[async_trait] impl InitCreateAccountRpcOps for UtxoStandardCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn init_create_account_rpc( &self, diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index 3b11824444..bcd7cc991f 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -13,9 +13,9 @@ use crate::rpc_command::init_scan_for_new_addresses::{InitScanAddressesRpcOps, S use crate::utxo::qtum::{qtum_coin_with_priv_key, QtumCoin, QtumDelegationOps, QtumDelegationRequest}; #[cfg(not(target_arch = "wasm32"))] use crate::utxo::rpc_clients::{BlockHashOrHeight, NativeUnspent}; -use crate::utxo::rpc_clients::{ElectrumBalance, ElectrumClient, ElectrumClientImpl, GetAddressInfoRes, - ListSinceBlockRes, NativeClient, NativeClientImpl, NetworkInfo, UtxoRpcClientOps, - ValidateAddressRes, VerboseBlock}; +use crate::utxo::rpc_clients::{ElectrumBalance, ElectrumClient, ElectrumClientImpl, ElectrumClientSettings, + GetAddressInfoRes, ListSinceBlockRes, NativeClient, NativeClientImpl, NetworkInfo, + UtxoRpcClientOps, ValidateAddressRes, VerboseBlock}; use crate::utxo::spv::SimplePaymentVerification; #[cfg(not(target_arch = "wasm32"))] use crate::utxo::utxo_block_header_storage::{BlockHeaderStorage, SqliteBlockHeadersStorage}; @@ -27,20 +27,19 @@ use crate::utxo::utxo_common_tests::{self, utxo_coin_fields_for_test, utxo_coin_ use crate::utxo::utxo_hd_wallet::UtxoHDAccount; use crate::utxo::utxo_standard::{utxo_standard_coin_with_priv_key, UtxoStandardCoin}; use crate::utxo::utxo_tx_history_v2::{UtxoTxDetailsParams, UtxoTxHistoryOps}; -use crate::{BlockHeightAndTime, CoinBalance, ConfirmPaymentInput, DexFee, IguanaPrivKey, PrivKeyBuildPolicy, - SearchForSwapTxSpendInput, SpendPaymentArgs, StakingInfosDetails, SwapOps, TradePreimageValue, - TxFeeDetails, TxMarshalingErr, ValidateFeeArgs, INVALID_SENDER_ERR_LOG}; +use crate::{BlockHeightAndTime, CoinBalance, CoinBalanceMap, ConfirmPaymentInput, DexFee, IguanaPrivKey, + PrivKeyBuildPolicy, SearchForSwapTxSpendInput, SpendPaymentArgs, StakingInfosDetails, SwapOps, + TradePreimageValue, TxFeeDetails, TxMarshalingErr, ValidateFeeArgs, INVALID_SENDER_ERR_LOG}; #[cfg(not(target_arch = "wasm32"))] use crate::{WaitForHTLCTxSpendArgs, WithdrawFee}; use chain::{BlockHeader, BlockHeaderBits, OutPoint}; use common::executor::Timer; -use common::{block_on, wait_until_sec, OrdRange, PagingOptionsEnum, DEX_FEE_ADDR_RAW_PUBKEY}; +use common::{block_on, block_on_f01, wait_until_sec, OrdRange, PagingOptionsEnum, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::{privkey::key_pair_from_seed, Bip44Chain, HDPathToAccount, RpcDerivationPath, Secp256k1Secret}; #[cfg(not(target_arch = "wasm32"))] use db_common::sqlite::rusqlite::Connection; use futures::channel::mpsc::channel; -use futures::future::join_all; -use futures::TryFutureExt; +use futures::future::{join_all, Either, FutureExt, TryFutureExt}; use keys::prefixes::*; use mm2_core::mm_ctx::MmCtxBuilder; use mm2_number::bigdecimal::{BigDecimal, Signed}; @@ -86,7 +85,7 @@ pub fn electrum_client_for_test(servers: &[&str]) -> ElectrumClient { let servers = servers.into_iter().map(|s| json::from_value(s).unwrap()).collect(); let abortable_system = AbortableQueue::default(); - block_on(builder.electrum_client(abortable_system, args, servers, None)).unwrap() + block_on(builder.electrum_client(abortable_system, args, servers, (None, None), None)).unwrap() } /// Returned client won't work by default, requires some mocks to be usable @@ -438,18 +437,16 @@ fn test_wait_for_payment_spend_timeout_native() { let wait_until = now_sec() - 1; let from_block = 1000; - assert!(coin - .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &transaction, - secret_hash: &[], - wait_until, - from_block, - swap_contract_address: &None, - check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: false - }) - .wait() - .is_err()); + assert!(block_on(coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &transaction, + secret_hash: &[], + wait_until, + from_block, + swap_contract_address: &None, + check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, + watcher_reward: false + })) + .is_err()); assert!(unsafe { OUTPUT_SPEND_CALLED }); } @@ -471,33 +468,40 @@ fn test_wait_for_payment_spend_timeout_electrum() { }; let abortable_system = AbortableQueue::default(); - let client = ElectrumClientImpl::new( - TEST_COIN_NAME.into(), + let client_settings = ElectrumClientSettings { + client_name: "test".to_string(), + servers: vec![], + coin_ticker: TEST_COIN_NAME.into(), + spawn_ping: true, + negotiate_version: true, + min_connected: 1, + max_connected: 1, + }; + let client = ElectrumClient::try_new( + client_settings, Default::default(), block_headers_storage, abortable_system, - true, None, - ); - let client = UtxoRpcClientEnum::Electrum(ElectrumClient(Arc::new(client))); + ) + .expect("Expected electrum_client_impl constructed without a problem"); + let client = UtxoRpcClientEnum::Electrum(client); let coin = utxo_coin_for_test(client, None, false); let transaction = hex::decode("01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000") .unwrap(); let wait_until = now_sec() - 1; let from_block = 1000; - assert!(coin - .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &transaction, - secret_hash: &[], - wait_until, - from_block, - swap_contract_address: &None, - check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: false - }) - .wait() - .is_err()); + assert!(block_on(coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &transaction, + secret_hash: &[], + wait_until, + from_block, + swap_contract_address: &None, + check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, + watcher_reward: false + })) + .is_err()); assert!(unsafe { OUTPUT_SPEND_CALLED }); } @@ -575,19 +579,22 @@ fn test_search_for_swap_tx_spend_electrum_was_refunded() { #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_impl_set_fixed_fee() { UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let client = NativeClient(Arc::new(NativeClientImpl::default())); @@ -613,7 +620,7 @@ fn test_withdraw_impl_set_fixed_fee() { } .into(), ); - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); assert_eq!(expected, tx_details.fee_details); } @@ -621,19 +628,22 @@ fn test_withdraw_impl_set_fixed_fee() { #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_impl_sat_per_kb_fee() { UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let client = NativeClient(Arc::new(NativeClientImpl::default())); @@ -662,7 +672,7 @@ fn test_withdraw_impl_sat_per_kb_fee() { } .into(), ); - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); assert_eq!(expected, tx_details.fee_details); } @@ -670,19 +680,22 @@ fn test_withdraw_impl_sat_per_kb_fee() { #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max() { UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let client = NativeClient(Arc::new(NativeClientImpl::default())); @@ -701,7 +714,7 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max() { memo: None, ibc_source_channel: None, }; - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); // The resulting transaction size might be 210 or 211 bytes depending on signature size // MM2 always expects the worst case during fee calculation // 0.1 * 211 / 1000 = 0.0211 @@ -721,19 +734,22 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max() { #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max_dust_included_to_fee() { UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let client = NativeClient(Arc::new(NativeClientImpl::default())); @@ -752,7 +768,7 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max_dust_included_to_fee() memo: None, ibc_source_channel: None, }; - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); // The resulting transaction size might be 210 or 211 bytes depending on signature size // MM2 always expects the worst case during fee calculation // 0.1 * 211 / 1000 = 0.0211 @@ -772,19 +788,22 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max_dust_included_to_fee() #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_impl_sat_per_kb_fee_amount_over_max() { UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let client = NativeClient(Arc::new(NativeClientImpl::default())); @@ -803,26 +822,29 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_over_max() { memo: None, ibc_source_channel: None, }; - coin.withdraw(withdraw_req).wait().unwrap_err(); + block_on_f01(coin.withdraw(withdraw_req)).unwrap_err(); } #[test] #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_impl_sat_per_kb_fee_max() { UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let client = NativeClient(Arc::new(NativeClientImpl::default())); @@ -851,7 +873,7 @@ fn test_withdraw_impl_sat_per_kb_fee_max() { } .into(), ); - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); assert_eq!(expected, tx_details.fee_details); } @@ -866,20 +888,23 @@ fn test_withdraw_kmd_rewards_impl( let verbose: RpcTransaction = json::from_str(verbose_serialized).unwrap(); let unspent_height = verbose.height; UtxoStandardCoin::get_unspent_ordered_list.mock_safe(move |coin: &UtxoStandardCoin, _| { - let tx: UtxoTx = tx_hex.into(); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: tx.hash(), - index: 0, - }, - value: tx.outputs[0].value, - height: unspent_height, - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let tx: UtxoTx = tx_hex.into(); + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: tx.hash(), + index: 0, + }, + value: tx.outputs[0].value, + height: unspent_height, + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); UtxoStandardCoin::get_current_mtp .mock_safe(move |_fields| MockResult::Return(Box::pin(futures::future::ok(current_mtp)))); @@ -909,7 +934,7 @@ fn test_withdraw_kmd_rewards_impl( coin: Some("KMD".into()), amount: "0.00001".parse().unwrap(), }); - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); assert_eq!(tx_details.fee_details, Some(expected_fee)); let expected_rewards = expected_rewards.map(|amount| KmdRewardsDetails { @@ -958,20 +983,23 @@ fn test_withdraw_rick_rewards_none() { const TX_HEX: &str = "0400008085202f8901df8119c507aa61d32332cd246dbfeb3818a4f96e76492454c1fbba5aa097977e000000004847304402205a7e229ea6929c97fd6dde254c19e4eb890a90353249721701ae7a1c477d99c402206a8b7c5bf42b5095585731d6b4c589ce557f63c20aed69ff242eca22ecfcdc7a01feffffff02d04d1bffbc050000232102afdbba3e3c90db5f0f4064118f79cf308f926c68afd64ea7afc930975663e4c4ac402dd913000000001976a9143e17014eca06281ee600adffa34b4afb0922a22288ac2bdab86035a00e000000000000000000000000"; UtxoStandardCoin::get_unspent_ordered_list.mock_safe(move |coin, _| { - let tx: UtxoTx = TX_HEX.into(); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: tx.hash(), - index: 0, - }, - value: tx.outputs[0].value, - height: Some(1431628), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let tx: UtxoTx = TX_HEX.into(); + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: tx.hash(), + index: 0, + }, + value: tx.outputs[0].value, + height: Some(1431628), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let withdraw_req = WithdrawRequest { @@ -988,7 +1016,7 @@ fn test_withdraw_rick_rewards_none() { coin: Some(TEST_COIN_NAME.into()), amount: "0.00001".parse().unwrap(), }); - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); assert_eq!(tx_details.fee_details, Some(expected_fee)); assert_eq!(tx_details.kmd_rewards, None); } @@ -1066,11 +1094,11 @@ fn test_electrum_rpc_client_error() { let client = electrum_client_for_test(&["electrum1.cipig.net:10060"]); let empty_hash = H256Json::default(); - let err = client.get_verbose_transaction(&empty_hash).wait().unwrap_err(); + let err = block_on_f01(client.get_verbose_transaction(&empty_hash)).unwrap_err(); // use the static string instead because the actual error message cannot be obtain // by serde_json serialization - let expected = r#"JsonRpcError { client_info: "coin: DOC", request: JsonRpcRequest { jsonrpc: "2.0", id: "1", method: "blockchain.transaction.get", params: [String("0000000000000000000000000000000000000000000000000000000000000000"), Bool(true)] }, error: Response(electrum1.cipig.net:10060, Object({"code": Number(2), "message": String("daemon error: DaemonError({'code': -5, 'message': 'No such mempool or blockchain transaction. Use gettransaction for wallet transactions.'})")})) }"#; + let expected = r#"method: "blockchain.transaction.get", params: [String("0000000000000000000000000000000000000000000000000000000000000000"), Bool(true)] }, error: Response(electrum1.cipig.net:10060, Object({"code": Number(2), "message": String("daemon error: DaemonError({'code': -5, 'message': 'No such mempool or blockchain transaction. Use gettransaction for wallet transactions.'})")})) }"#; let actual = format!("{}", err); assert!(actual.contains(expected)); @@ -1305,10 +1333,7 @@ fn test_get_median_time_past_from_electrum_kmd() { "electrum3.cipig.net:10001", ]); - let mtp = client - .get_median_time_past(1773390, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard) - .wait() - .unwrap(); + let mtp = block_on_f01(client.get_median_time_past(1773390, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard)).unwrap(); // the MTP is block time of 1773385 in this case assert_eq!(1583159915, mtp); } @@ -1321,10 +1346,7 @@ fn test_get_median_time_past_from_electrum_btc() { "electrum3.cipig.net:10000", ]); - let mtp = client - .get_median_time_past(632858, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard) - .wait() - .unwrap(); + let mtp = block_on_f01(client.get_median_time_past(632858, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard)).unwrap(); assert_eq!(1591173041, mtp); } @@ -1348,10 +1370,7 @@ fn test_get_median_time_past_from_native_has_median_in_get_block() { ) }); - let mtp = client - .get_median_time_past(632858, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard) - .wait() - .unwrap(); + let mtp = block_on_f01(client.get_median_time_past(632858, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard)).unwrap(); assert_eq!(1591173041, mtp); } @@ -1394,10 +1413,7 @@ fn test_get_median_time_past_from_native_does_not_have_median_in_get_block() { MockResult::Return(Box::new(futures01::future::ok(block))) }); - let mtp = client - .get_median_time_past(632858, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard) - .wait() - .unwrap(); + let mtp = block_on_f01(client.get_median_time_past(632858, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard)).unwrap(); assert_eq!(1591173041, mtp); } @@ -1526,33 +1542,44 @@ fn test_network_info_negative_time_offset() { #[test] fn test_unavailable_electrum_proto_version() { - ElectrumClientImpl::new.mock_safe( - |coin_ticker, event_handlers, block_headers_storage, abortable_system, _, _| { + ElectrumClientImpl::try_new_arc.mock_safe( + |client_settings, block_headers_storage, abortable_system, event_handlers, scripthash_notification_sender| { MockResult::Return(ElectrumClientImpl::with_protocol_version( - coin_ticker, - event_handlers, - OrdRange::new(1.8, 1.9).unwrap(), + client_settings, block_headers_storage, abortable_system, - None, + event_handlers, + scripthash_notification_sender, + OrdRange::new(1.8, 1.9).unwrap(), )) }, ); let conf = json!({"coin":"RICK","asset":"RICK","rpcport":8923}); + let servers = ["electrum1.cipig.net:10020"]; let req = json!({ "method": "electrum", - "servers": [{"url":"electrum1.cipig.net:10020"}], + "servers": servers.iter().map(|server| json!({"url": server})).collect::>(), }); let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); let priv_key = Secp256k1Secret::from([1; 32]); - let error = block_on(utxo_standard_coin_with_priv_key(&ctx, "RICK", &conf, ¶ms, priv_key)) - .err() - .unwrap(); - log!("Error: {}", error); - assert!(error.contains("There are no Electrums with the required protocol version")); + let coin = block_on(utxo_standard_coin_with_priv_key(&ctx, "RICK", &conf, ¶ms, priv_key)).unwrap(); + // Wait a little bit to make sure the servers are removed due to version mismatch. + block_on(Timer::sleep(2.)); + if let UtxoRpcClientEnum::Electrum(ref electrum_client) = coin.as_ref().rpc_client { + for server in servers { + let error = block_on(electrum_client.get_block_count_from(server).compat()) + .err() + .unwrap() + .to_string(); + log!("{}", error); + assert!(error.contains("Unknown server address")); + } + } else { + panic!("Expected Electrum client"); + } } #[test] @@ -1595,20 +1622,29 @@ fn test_spam_rick() { #[test] fn test_one_unavailable_electrum_proto_version() { + // Patch the electurm client construct to require protocol version 1.4 only. + ElectrumClientImpl::try_new_arc.mock_safe( + |client_settings, block_headers_storage, abortable_system, event_handlers, scripthash_notification_sender| { + MockResult::Return(ElectrumClientImpl::with_protocol_version( + client_settings, + block_headers_storage, + abortable_system, + event_handlers, + scripthash_notification_sender, + OrdRange::new(1.4, 1.4).unwrap(), + )) + }, + ); // check if the electrum-mona.bitbank.cc:50001 doesn't support the protocol version 1.4 let client = electrum_client_for_test(&["electrum-mona.bitbank.cc:50001"]); - let result = client - .server_version( - "electrum-mona.bitbank.cc:50001", - "AtomicDEX", - &OrdRange::new(1.4, 1.4).unwrap(), - ) - .wait(); - assert!(result - .err() - .unwrap() - .to_string() - .contains("unsupported protocol version")); + // When an electrum server doesn't support our protocol version range, it gets removed by the client, + // wait a little bit to make sure this is the case. + block_on(Timer::sleep(2.)); + let error = block_on_f01(client.get_block_count_from("electrum-mona.bitbank.cc:50001")) + .unwrap_err() + .to_string(); + log!("{}", error); + assert!(error.contains("Unknown server address")); drop(client); log!("Run BTC coin to test the server.version loop"); @@ -1628,7 +1664,7 @@ fn test_one_unavailable_electrum_proto_version() { block_on(async { Timer::sleep(0.5).await }); - assert!(coin.as_ref().rpc_client.get_block_count().wait().is_ok()); + assert!(block_on_f01(coin.as_ref().rpc_client.get_block_count()).is_ok()); } #[test] @@ -1685,7 +1721,7 @@ fn test_qtum_add_delegation() { address: address.to_string(), fee: Some(10), }; - let res = coin.add_delegation(request).wait().unwrap(); + let res = block_on_f01(coin.add_delegation(request)).unwrap(); // Eligible for delegation assert!(res.my_balance_change.is_negative()); assert_eq!(res.total_amount, res.spent_by_me); @@ -1695,7 +1731,7 @@ fn test_qtum_add_delegation() { address: "fake_address".to_string(), fee: Some(10), }; - let res = coin.add_delegation(request).wait(); + let res = block_on_f01(coin.add_delegation(request)); // Wrong address assert!(res.is_err()); } @@ -1728,7 +1764,7 @@ fn test_qtum_add_delegation_on_already_delegating() { address: address.to_string(), fee: Some(10), }; - let res = coin.add_delegation(request).wait(); + let res = block_on_f01(coin.add_delegation(request)); // Already Delegating assert!(res.is_err()); } @@ -1754,7 +1790,7 @@ fn test_qtum_get_delegation_infos() { keypair.private().secret, )) .unwrap(); - let staking_infos = coin.get_delegation_infos().wait().unwrap(); + let staking_infos = block_on_f01(coin.get_delegation_infos()).unwrap(); match staking_infos.staking_infos_details { StakingInfosDetails::Qtum(staking_details) => { assert!(staking_details.am_i_staking); @@ -1784,49 +1820,49 @@ fn test_qtum_remove_delegation() { keypair.private().secret, )) .unwrap(); - let res = coin.remove_delegation().wait(); + let res = block_on_f01(coin.remove_delegation()); assert!(res.is_ok()); } #[test] fn test_qtum_my_balance() { QtumCoin::get_mature_unspent_ordered_list.mock_safe(move |coin, _address| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - // spendable balance (66.0) - let mature = vec![ - UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + // spendable balance (66.0) + let mature = vec![ + UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 5000000000, + height: Default::default(), + script: Vec::new().into(), }, - value: 5000000000, - height: Default::default(), - script: Vec::new().into(), - }, - UnspentInfo { + UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1600000000, + height: Default::default(), + script: Vec::new().into(), + }, + ]; + // unspendable (2.0) + let immature = vec![UnspentInfo { outpoint: OutPoint { hash: 1.into(), index: 0, }, - value: 1600000000, + value: 200000000, height: Default::default(), script: Vec::new().into(), - }, - ]; - // unspendable (2.0) - let immature = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 200000000, - height: Default::default(), - script: Vec::new().into(), - }]; - MockResult::Return(Box::pin(futures::future::ok(( - MatureUnspentList { mature, immature }, - cache, - )))) + }]; + Ok((MatureUnspentList { mature, immature }, cache)) + }; + MockResult::Return(fut.boxed()) }); let conf = json!({"coin":"tQTUM","rpcport":13889,"pubtype":120,"p2shtype":110}); @@ -1845,7 +1881,7 @@ fn test_qtum_my_balance() { let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); let coin = block_on(qtum_coin_with_priv_key(&ctx, "tQTUM", &conf, ¶ms, priv_key)).unwrap(); - let CoinBalance { spendable, unspendable } = coin.my_balance().wait().unwrap(); + let CoinBalance { spendable, unspendable } = block_on_f01(coin.my_balance()).unwrap(); let expected_spendable = BigDecimal::from(66); let expected_unspendable = BigDecimal::from(2); assert_eq!(spendable, expected_spendable); @@ -1881,7 +1917,7 @@ fn test_qtum_my_balance_with_check_utxo_maturity_false() { let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); let coin = block_on(qtum_coin_with_priv_key(&ctx, "tQTUM", &conf, ¶ms, priv_key)).unwrap(); - let CoinBalance { spendable, unspendable } = coin.my_balance().wait().unwrap(); + let CoinBalance { spendable, unspendable } = block_on_f01(coin.my_balance()).unwrap(); let expected_spendable = BigDecimal::from(DISPLAY_BALANCE); let expected_unspendable = BigDecimal::from(0); assert_eq!(spendable, expected_spendable); @@ -1899,7 +1935,7 @@ fn test_get_mature_unspent_ordered_map_from_cache_impl( const TX_HASH: &str = "b43f9ed47f7b97d4766b6f1614136fa0c55b9a52c97342428333521fa13ad714"; let tx_hash: H256Json = hex::decode(TX_HASH).unwrap().as_slice().into(); let client = electrum_client_for_test(DOC_ELECTRUM_ADDRS); - let mut verbose = client.get_verbose_transaction(&tx_hash).wait().unwrap(); + let mut verbose = block_on_f01(client.get_verbose_transaction(&tx_hash)).unwrap(); verbose.confirmations = cached_confs; verbose.height = cached_height; @@ -2517,15 +2553,13 @@ fn test_find_output_spend_skips_conflicting_transactions() { let tx: UtxoTx = "0400008085202f89027f57730fcbbc2c72fb18bcc3766a713044831a117bb1cade3ed88644864f7333020000006a47304402206e3737b2fcf078b61b16fa67340cc3e79c5d5e2dc9ffda09608371552a3887450220460a332aa1b8ad8f2de92d319666f70751078b221199951f80265b4f7cef8543012102d8c948c6af848c588517288168faa397d6ba3ea924596d03d1d84f224b5123c2ffffffff42b916a80430b80a77e114445b08cf120735447a524de10742fac8f6a9d4170f000000006a473044022004aa053edafb9d161ea8146e0c21ed1593aa6b9404dd44294bcdf920a1695fd902202365eac15dbcc5e9f83e2eed56a8f2f0e5aded36206f9c3fabc668fd4665fa2d012102d8c948c6af848c588517288168faa397d6ba3ea924596d03d1d84f224b5123c2ffffffff03547b16000000000017a9143e8ad0e2bf573d32cb0b3d3a304d9ebcd0c2023b870000000000000000166a144e2b3c0323ab3c2dc6f86dc5ec0729f11e42f56103970400000000001976a91450f4f098306f988d8843004689fae28c83ef16e888ac89c5925f000000000000000000000000000000".into(); let vout = 0; let from_block = 0; - let actual = client - .find_output_spend( - tx.hash(), - &tx.outputs[vout].script_pubkey, - vout, - BlockHashOrHeight::Height(from_block), - TxHashAlgo::DSHA256, - ) - .wait(); + let actual = block_on_f01(client.find_output_spend( + tx.hash(), + &tx.outputs[vout].script_pubkey, + vout, + BlockHashOrHeight::Height(from_block), + TxHashAlgo::DSHA256, + )); assert_eq!(actual, Ok(None)); assert_eq!(unsafe { GET_RAW_TRANSACTION_BYTES_CALLED }, 1); } @@ -2609,7 +2643,7 @@ fn test_get_sender_trade_fee_dynamic_tx_fee() { ); coin_fields.tx_fee = TxFee::Dynamic(EstimateFeeMethod::Standard); let coin = utxo_coin_from_fields(coin_fields); - let my_balance = coin.my_spendable_balance().wait().expect("!my_balance"); + let my_balance = block_on_f01(coin.my_spendable_balance()).expect("!my_balance"); let expected_balance = BigDecimal::from_str("2.22222").expect("!BigDecimal::from_str"); assert_eq!(my_balance, expected_balance); @@ -2657,7 +2691,7 @@ fn test_validate_fee_wrong_sender() { min_block_number: 0, uuid: &[], }; - let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let error = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains(INVALID_SENDER_ERR_LOG)), @@ -2682,7 +2716,7 @@ fn test_validate_fee_min_block() { min_block_number: 278455, uuid: &[], }; - let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let error = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), _ => panic!("Expected `WrongPaymentTx` early confirmation, found {:?}", error), @@ -2711,7 +2745,7 @@ fn test_validate_fee_bch_70_bytes_signature() { min_block_number: 0, uuid: &[], }; - coin.validate_fee(validate_fee_args).wait().unwrap(); + block_on(coin.validate_fee(validate_fee_args)).unwrap(); } #[test] @@ -2766,7 +2800,7 @@ fn firo_lelantus_tx() { "electrumx02.firo.org:50001", "electrumx03.firo.org:50001", ]); - let _tx = electrum.get_verbose_transaction(&tx_hash).wait().unwrap(); + let _tx = block_on_f01(electrum.get_verbose_transaction(&tx_hash)).unwrap(); } #[test] @@ -2903,9 +2937,7 @@ fn doge_mtp() { "electrum2.cipig.net:10060", "electrum3.cipig.net:10060", ]); - let mtp = electrum - .get_median_time_past(3631820, NonZeroU64::new(11).unwrap(), CoinVariant::Standard) - .wait() + let mtp = block_on_f01(electrum.get_median_time_past(3631820, NonZeroU64::new(11).unwrap(), CoinVariant::Standard)) .unwrap(); assert_eq!(mtp, 1614849084); } @@ -2917,9 +2949,7 @@ fn firo_mtp() { "electrumx02.firo.org:50001", "electrumx03.firo.org:50001", ]); - let mtp = electrum - .get_median_time_past(356730, NonZeroU64::new(11).unwrap(), CoinVariant::Standard) - .wait() + let mtp = block_on_f01(electrum.get_median_time_past(356730, NonZeroU64::new(11).unwrap(), CoinVariant::Standard)) .unwrap(); assert_eq!(mtp, 1616492629); } @@ -2927,9 +2957,7 @@ fn firo_mtp() { #[test] fn verus_mtp() { let electrum = electrum_client_for_test(&["el0.verus.io:17485", "el1.verus.io:17485", "el2.verus.io:17485"]); - let mtp = electrum - .get_median_time_past(1480113, NonZeroU64::new(11).unwrap(), CoinVariant::Standard) - .wait() + let mtp = block_on_f01(electrum.get_median_time_past(1480113, NonZeroU64::new(11).unwrap(), CoinVariant::Standard)) .unwrap(); assert_eq!(mtp, 1618579909); } @@ -2941,9 +2969,7 @@ fn sys_mtp() { "electrum2.cipig.net:10064", "electrum3.cipig.net:10064", ]); - let mtp = electrum - .get_median_time_past(1006678, NonZeroU64::new(11).unwrap(), CoinVariant::Standard) - .wait() + let mtp = block_on_f01(electrum.get_median_time_past(1006678, NonZeroU64::new(11).unwrap(), CoinVariant::Standard)) .unwrap(); assert_eq!(mtp, 1620019628); } @@ -2955,9 +2981,7 @@ fn btc_mtp() { "electrum2.cipig.net:10000", "electrum3.cipig.net:10000", ]); - let mtp = electrum - .get_median_time_past(681659, NonZeroU64::new(11).unwrap(), CoinVariant::Standard) - .wait() + let mtp = block_on_f01(electrum.get_median_time_past(681659, NonZeroU64::new(11).unwrap(), CoinVariant::Standard)) .unwrap(); assert_eq!(mtp, 1620019527); } @@ -2969,9 +2993,7 @@ fn rvn_mtp() { "electrum2.cipig.net:10051", "electrum3.cipig.net:10051", ]); - let mtp = electrum - .get_median_time_past(1968120, NonZeroU64::new(11).unwrap(), CoinVariant::Standard) - .wait() + let mtp = block_on_f01(electrum.get_median_time_past(1968120, NonZeroU64::new(11).unwrap(), CoinVariant::Standard)) .unwrap(); assert_eq!(mtp, 1633946264); } @@ -2983,10 +3005,8 @@ fn qtum_mtp() { "electrum2.cipig.net:10050", "electrum3.cipig.net:10050", ]); - let mtp = electrum - .get_median_time_past(681659, NonZeroU64::new(11).unwrap(), CoinVariant::Qtum) - .wait() - .unwrap(); + let mtp = + block_on_f01(electrum.get_median_time_past(681659, NonZeroU64::new(11).unwrap(), CoinVariant::Qtum)).unwrap(); assert_eq!(mtp, 1598854128); } @@ -2997,9 +3017,7 @@ fn zer_mtp() { "electrum2.cipig.net:10065", "electrum3.cipig.net:10065", ]); - let mtp = electrum - .get_median_time_past(1130915, NonZeroU64::new(11).unwrap(), CoinVariant::Standard) - .wait() + let mtp = block_on_f01(electrum.get_median_time_past(1130915, NonZeroU64::new(11).unwrap(), CoinVariant::Standard)) .unwrap(); assert_eq!(mtp, 1623240214); } @@ -3196,7 +3214,7 @@ fn test_withdraw_to_p2pk_fails() { }; assert!(matches!( - coin.withdraw(withdraw_req).wait().unwrap_err().into_inner(), + block_on_f01(coin.withdraw(withdraw_req)).unwrap_err().into_inner(), WithdrawError::InvalidAddress(..) )) } @@ -3209,19 +3227,22 @@ fn test_withdraw_to_p2pkh() { let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); // Create a p2pkh address for the test coin @@ -3249,7 +3270,7 @@ fn test_withdraw_to_p2pkh() { memo: None, ibc_source_channel: None, }; - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); let transaction: UtxoTx = deserialize(tx_details.tx.tx_hex().unwrap().as_slice()).unwrap(); let output_script: Script = transaction.outputs[0].script_pubkey.clone().into(); @@ -3266,19 +3287,22 @@ fn test_withdraw_to_p2sh() { let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); // Create a p2sh address for the test coin @@ -3306,7 +3330,7 @@ fn test_withdraw_to_p2sh() { memo: None, ibc_source_channel: None, }; - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); let transaction: UtxoTx = deserialize(tx_details.tx.tx_hex().unwrap().as_slice()).unwrap(); let output_script: Script = transaction.outputs[0].script_pubkey.clone().into(); @@ -3323,19 +3347,22 @@ fn test_withdraw_to_p2wpkh() { let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, true); UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); // Create a p2wpkh address for the test coin @@ -3363,7 +3390,7 @@ fn test_withdraw_to_p2wpkh() { memo: None, ibc_source_channel: None, }; - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); let transaction: UtxoTx = deserialize(tx_details.tx.tx_hex().unwrap().as_slice()).unwrap(); let output_script: Script = transaction.outputs[0].script_pubkey.clone().into(); @@ -3380,22 +3407,29 @@ fn test_withdraw_p2pk_balance() { let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - // Use a p2pk output script for this UTXO - script: output_script_p2pk( - &block_on(coin.as_ref().derivation_method.unwrap_single_addr()) - .pubkey() - .unwrap(), - ), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + // Use a p2pk output script for this UTXO + script: output_script_p2pk( + &coin + .as_ref() + .derivation_method + .unwrap_single_addr() + .await + .pubkey() + .unwrap(), + ), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); // Create a dummy p2pkh address to withdraw the coins to. @@ -3411,7 +3445,7 @@ fn test_withdraw_p2pk_balance() { memo: None, ibc_source_channel: None, }; - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); let transaction: UtxoTx = deserialize(tx_details.tx.tx_hex().unwrap().as_slice()).unwrap(); // The change should be in a p2pkh script. @@ -3432,8 +3466,11 @@ fn test_utxo_standard_with_check_utxo_maturity_true() { UtxoStandardCoin::get_mature_unspent_ordered_list.mock_safe(|coin, _| { unsafe { GET_MATURE_UNSPENT_ORDERED_LIST_CALLED = true }; - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - MockResult::Return(Box::pin(futures::future::ok((MatureUnspentList::default(), cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + Ok((MatureUnspentList::default(), cache)) + }; + MockResult::Return(fut.boxed()) }); let conf = json!({"coin":"RICK","asset":"RICK","rpcport":25435,"txversion":4,"overwintered":1,"mm2":1,"protocol":{"type":"UTXO"}}); @@ -3451,7 +3488,7 @@ fn test_utxo_standard_with_check_utxo_maturity_true() { let address = Address::from_legacyaddress("R9o9xTocqr6CeEDGDH6mEYpwLoMz6jNjMW", &KMD_PREFIXES).unwrap(); // Don't use `block_on` here because it's used within a mock of [`GetUtxoListOps::get_mature_unspent_ordered_list`]. - coin.get_unspent_ordered_list(&address).compat().wait().unwrap(); + block_on_f01(coin.get_unspent_ordered_list(&address).compat()).unwrap(); assert!(unsafe { GET_MATURE_UNSPENT_ORDERED_LIST_CALLED }); } @@ -3464,9 +3501,11 @@ fn test_utxo_standard_without_check_utxo_maturity() { UtxoStandardCoin::get_all_unspent_ordered_list.mock_safe(|coin, _| { unsafe { GET_ALL_UNSPENT_ORDERED_LIST_CALLED = true }; - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = Vec::new(); - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + Ok((Vec::new(), cache)) + }; + MockResult::Return(fut.boxed()) }); UtxoStandardCoin::get_mature_unspent_ordered_list.mock_safe(|_, _| { @@ -3487,7 +3526,7 @@ fn test_utxo_standard_without_check_utxo_maturity() { let address = Address::from_legacyaddress("R9o9xTocqr6CeEDGDH6mEYpwLoMz6jNjMW", &KMD_PREFIXES).unwrap(); // Don't use `block_on` here because it's used within a mock of [`UtxoStandardCoin::get_all_unspent_ordered_list`]. - coin.get_unspent_ordered_list(&address).compat().wait().unwrap(); + block_on_f01(coin.get_unspent_ordered_list(&address).compat()).unwrap(); assert!(unsafe { GET_ALL_UNSPENT_ORDERED_LIST_CALLED }); } @@ -3500,8 +3539,11 @@ fn test_qtum_without_check_utxo_maturity() { QtumCoin::get_mature_unspent_ordered_list.mock_safe(|coin, _| { unsafe { GET_MATURE_UNSPENT_ORDERED_LIST_CALLED = true }; - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - MockResult::Return(Box::pin(futures::future::ok((MatureUnspentList::default(), cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + Ok((MatureUnspentList::default(), cache)) + }; + MockResult::Return(fut.boxed()) }); let conf = json!({"coin":"tQTUM","rpcport":13889,"pubtype":120,"p2shtype":110}); @@ -3526,7 +3568,7 @@ fn test_qtum_without_check_utxo_maturity() { ) .unwrap(); // Don't use `block_on` here because it's used within a mock of [`QtumCoin::get_mature_unspent_ordered_list`]. - coin.get_unspent_ordered_list(&address).compat().wait().unwrap(); + block_on_f01(coin.get_unspent_ordered_list(&address).compat()).unwrap(); assert!(unsafe { GET_MATURE_UNSPENT_ORDERED_LIST_CALLED }); } @@ -3604,9 +3646,12 @@ fn test_qtum_with_check_utxo_maturity_false() { QtumCoin::get_all_unspent_ordered_list.mock_safe(|coin, _address| { unsafe { GET_ALL_UNSPENT_ORDERED_LIST_CALLED = true }; - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = Vec::new(); - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = Vec::new(); + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); QtumCoin::get_mature_unspent_ordered_list.mock_safe(|_, _| { panic!( @@ -3637,14 +3682,14 @@ fn test_qtum_with_check_utxo_maturity_false() { ) .unwrap(); // Don't use `block_on` here because it's used within a mock of [`QtumCoin::get_all_unspent_ordered_list`]. - coin.get_unspent_ordered_list(&address).compat().wait().unwrap(); + block_on_f01(coin.get_unspent_ordered_list(&address).compat()).unwrap(); assert!(unsafe { GET_ALL_UNSPENT_ORDERED_LIST_CALLED }); } #[test] fn test_account_balance_rpc() { let mut addresses_map: HashMap = HashMap::new(); - let mut balances_by_der_path: HashMap> = HashMap::new(); + let mut balances_by_der_path: HashMap> = HashMap::new(); macro_rules! known_address { ($der_path:literal, $address:literal, $chain:expr, balance = $balance:literal) => { @@ -3653,7 +3698,10 @@ fn test_account_balance_rpc() { address: $address.to_string(), derivation_path: RpcDerivationPath(DerivationPath::from_str($der_path).unwrap()), chain: $chain, - balance: CoinBalance::new(BigDecimal::from($balance)), + balance: HashMap::from([( + TEST_COIN_NAME.to_string(), + CoinBalance::new(BigDecimal::from($balance)), + )]), }) }; } @@ -3743,7 +3791,7 @@ fn test_account_balance_rpc() { account_index: 0, derivation_path: DerivationPath::from_str("m/44'/141'/0'").unwrap().into(), addresses: get_balances!("m/44'/141'/0'/0/0", "m/44'/141'/0'/0/1", "m/44'/141'/0'/0/2"), - page_balance: CoinBalance::new(BigDecimal::from(0)), + page_balance: HashMap::from([(coin.ticker().to_string(), CoinBalance::new(BigDecimal::from(0)))]), limit: 3, skipped: 0, total: 7, @@ -3765,7 +3813,7 @@ fn test_account_balance_rpc() { account_index: 0, derivation_path: DerivationPath::from_str("m/44'/141'/0'").unwrap().into(), addresses: get_balances!("m/44'/141'/0'/0/3", "m/44'/141'/0'/0/4", "m/44'/141'/0'/0/5"), - page_balance: CoinBalance::new(BigDecimal::from(99)), + page_balance: HashMap::from([(coin.ticker().to_string(), CoinBalance::new(BigDecimal::from(99)))]), limit: 3, skipped: 3, total: 7, @@ -3787,7 +3835,7 @@ fn test_account_balance_rpc() { account_index: 0, derivation_path: DerivationPath::from_str("m/44'/141'/0'").unwrap().into(), addresses: get_balances!("m/44'/141'/0'/0/6"), - page_balance: CoinBalance::new(BigDecimal::from(32)), + page_balance: HashMap::from([(coin.ticker().to_string(), CoinBalance::new(BigDecimal::from(32)))]), limit: 3, skipped: 6, total: 7, @@ -3809,7 +3857,7 @@ fn test_account_balance_rpc() { account_index: 0, derivation_path: DerivationPath::from_str("m/44'/141'/0'").unwrap().into(), addresses: Vec::new(), - page_balance: CoinBalance::default(), + page_balance: HashMap::default(), limit: 3, skipped: 7, total: 7, @@ -3831,7 +3879,7 @@ fn test_account_balance_rpc() { account_index: 0, derivation_path: DerivationPath::from_str("m/44'/141'/0'").unwrap().into(), addresses: get_balances!("m/44'/141'/0'/1/1", "m/44'/141'/0'/1/2"), - page_balance: CoinBalance::new(BigDecimal::from(54)), + page_balance: HashMap::from([(coin.ticker().to_string(), CoinBalance::new(BigDecimal::from(54)))]), limit: 3, skipped: 1, total: 3, @@ -3853,7 +3901,7 @@ fn test_account_balance_rpc() { account_index: 1, derivation_path: DerivationPath::from_str("m/44'/141'/1'").unwrap().into(), addresses: Vec::new(), - page_balance: CoinBalance::default(), + page_balance: HashMap::default(), limit: 3, skipped: 0, total: 0, @@ -3875,7 +3923,7 @@ fn test_account_balance_rpc() { account_index: 1, derivation_path: DerivationPath::from_str("m/44'/141'/1'").unwrap().into(), addresses: get_balances!("m/44'/141'/1'/1/0"), - page_balance: CoinBalance::new(BigDecimal::from(0)), + page_balance: HashMap::from([(coin.ticker().to_string(), CoinBalance::new(BigDecimal::from(0)))]), limit: 3, skipped: 0, total: 1, @@ -3897,7 +3945,7 @@ fn test_account_balance_rpc() { account_index: 1, derivation_path: DerivationPath::from_str("m/44'/141'/1'").unwrap().into(), addresses: Vec::new(), - page_balance: CoinBalance::default(), + page_balance: HashMap::default(), limit: 3, skipped: 1, total: 1, @@ -3939,7 +3987,7 @@ fn test_scan_for_new_addresses() { // The list of addresses with a non-empty transaction history. let mut non_empty_addresses: HashSet = HashSet::new(); // The map of results by the addresses. - let mut balances_by_der_path: HashMap> = HashMap::new(); + let mut balances_by_der_path: HashMap> = HashMap::new(); macro_rules! new_address { ($der_path:literal, $address:literal, $chain:expr, balance = $balance:expr) => {{ @@ -3952,7 +4000,9 @@ fn test_scan_for_new_addresses() { address: $address.to_string(), derivation_path: RpcDerivationPath(DerivationPath::from_str($der_path).unwrap()), chain: $chain, - balance: CoinBalance::new(BigDecimal::from($balance.unwrap_or(0i32))), + balance: $balance.map_or_else(HashMap::default, |balance| { + HashMap::from([(TEST_COIN_NAME.to_string(), CoinBalance::new(BigDecimal::from(balance)))]) + }), }); }}; } @@ -3982,7 +4032,7 @@ fn test_scan_for_new_addresses() { // Account#0, internal addresses. new_address!("m/44'/141'/0'/1/1", "RPj9JXUVnewWwVpxZDeqGB25qVqz5qJzwP", Bip44Chain::Internal, balance = Some(98)); - new_address!("m/44'/141'/0'/1/2", "RSYdSLRYWuzBson2GDbWBa632q2PmFnCaH", Bip44Chain::Internal, balance = None); + new_address!("m/44'/141'/0'/1/2", "RSYdSLRYWuzBson2GDbWBa632q2PmFnCaH", Bip44Chain::Internal, balance = None::); new_address!("m/44'/141'/0'/1/3", "RQstQeTUEZLh6c3YWJDkeVTTQoZUsfvNCr", Bip44Chain::Internal, balance = Some(14)); unused_address!("m/44'/141'/0'/1/4", "RT54m6pfj9scqwSLmYdfbmPcrpxnWGAe9J"); unused_address!("m/44'/141'/0'/1/5", "RYWfEFxqA6zya9c891Dj7vxiDojCmuWR9T"); @@ -3992,8 +4042,8 @@ fn test_scan_for_new_addresses() { // Account#1, external addresses. new_address!("m/44'/141'/1'/0/0", "RBQFLwJ88gVcnfkYvJETeTAB6AAYLow12K", Bip44Chain::External, balance = Some(9)); new_address!("m/44'/141'/1'/0/1", "RCyy77sRWFa2oiFPpyimeTQfenM1aRoiZs", Bip44Chain::External, balance = Some(7)); - new_address!("m/44'/141'/1'/0/2", "RDnNa3pQmisfi42KiTZrfYfuxkLC91PoTJ", Bip44Chain::External, balance = None); - new_address!("m/44'/141'/1'/0/3", "RQRGgXcGJz93CoAfQJoLgBz2r9HtJYMX3Z", Bip44Chain::External, balance = None); + new_address!("m/44'/141'/1'/0/2", "RDnNa3pQmisfi42KiTZrfYfuxkLC91PoTJ", Bip44Chain::External, balance = None::); + new_address!("m/44'/141'/1'/0/3", "RQRGgXcGJz93CoAfQJoLgBz2r9HtJYMX3Z", Bip44Chain::External, balance = None::); new_address!("m/44'/141'/1'/0/4", "RM6cqSFCFZ4J1LngLzqKkwo2ouipbDZUbm", Bip44Chain::External, balance = Some(11)); unused_address!("m/44'/141'/1'/0/5", "RX2fGBZjNZMNdNcnc5QBRXvmsXTvadvTPN"); unused_address!("m/44'/141'/1'/0/6", "RJJ7muUETyp59vxVXna9KAZ9uQ1QSqmcjE"); @@ -4207,7 +4257,7 @@ fn test_get_new_address() { // ======= - let confirm_address = MockableConfirmAddress::default(); + let confirm_address = MockableConfirmAddress; expected_checked_addresses!["m/44'/141'/0'/0/3", "RU1gRFXWXNx7uPRAEJ7wdZAW1RZ4TE6Vv1"]; non_empty_addresses!["m/44'/141'/0'/0/3", "RU1gRFXWXNx7uPRAEJ7wdZAW1RZ4TE6Vv1"]; @@ -4365,7 +4415,9 @@ fn test_for_non_existent_tx_hex_utxo_electrum() { wait_until: timeout, check_every: 1, }; - let actual = coin.wait_for_confirmations(confirm_payment_input).wait().err().unwrap(); + let actual = block_on_f01(coin.wait_for_confirmations(confirm_payment_input)) + .err() + .unwrap(); assert!(actual.contains( "Tx d342ff9da528a2e262bddf2b6f9a27d1beb7aeb03f0fc8d9eac2987266447e44 was not found on chain after 10 tries" )); @@ -4408,10 +4460,7 @@ fn test_native_display_balances() { Address::from_legacyaddress("RJeDDtDRtKUoL8BCKdH7TNCHqUKr7kQRsi", &KMD_PREFIXES).unwrap(), Address::from_legacyaddress("RQHn9VPHBqNjYwyKfJbZCiaxVrWPKGQjeF", &KMD_PREFIXES).unwrap(), ]; - let actual = rpc_client - .display_balances(addresses, TEST_COIN_DECIMALS) - .wait() - .unwrap(); + let actual = block_on_f01(rpc_client.display_balances(addresses, TEST_COIN_DECIMALS)).unwrap(); let expected: Vec<(Address, BigDecimal)> = vec![ ( @@ -4549,7 +4598,6 @@ fn test_utxo_validate_valid_and_invalid_pubkey() { #[test] fn test_block_header_utxo_loop() { use crate::utxo::utxo_builder::{block_header_utxo_loop, BlockHeaderUtxoLoopExtraArgs}; - use futures::future::{Either, FutureExt}; use keys::hash::H256 as H256Json; static mut CURRENT_BLOCK_COUNT: u64 = 13; diff --git a/mm2src/coins/utxo/utxo_wasm_tests.rs b/mm2src/coins/utxo/utxo_wasm_tests.rs index a33e1ba039..bd059c8627 100644 --- a/mm2src/coins/utxo/utxo_wasm_tests.rs +++ b/mm2src/coins/utxo/utxo_wasm_tests.rs @@ -42,7 +42,7 @@ pub async fn electrum_client_for_test(servers: &[&str]) -> ElectrumClient { let servers = servers.into_iter().map(|s| json::from_value(s).unwrap()).collect(); let abortable_system = AbortableQueue::default(); builder - .electrum_client(abortable_system, args, servers, None) + .electrum_client(abortable_system, args, servers, (None, None), None) .await .unwrap() } diff --git a/mm2src/coins/utxo_signer/src/with_key_pair.rs b/mm2src/coins/utxo_signer/src/with_key_pair.rs index 8d67e23f9c..b62fa695f2 100644 --- a/mm2src/coins/utxo_signer/src/with_key_pair.rs +++ b/mm2src/coins/utxo_signer/src/with_key_pair.rs @@ -244,7 +244,7 @@ pub fn signature_hash_to_sign( } fn sign_message(message: &H256, key_pair: &KeyPair) -> UtxoSignWithKeyPairResult { - let signature = key_pair.private().sign(message)?; + let signature = key_pair.private().sign_low_r(message)?; Ok(Bytes::from(signature.to_vec())) } diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index d9a416d204..b8c7c8c944 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -11,7 +11,7 @@ use crate::coin_errors::{MyAddressError, ValidatePaymentResult}; use crate::hd_wallet::HDPathAccountToAddressId; use crate::my_tx_history_v2::{MyTxHistoryErrorV2, MyTxHistoryRequestV2, MyTxHistoryResponseV2}; use crate::rpc_command::init_withdraw::{InitWithdrawCoin, WithdrawInProgressStatus, WithdrawTaskHandleShared}; -use crate::utxo::rpc_clients::{ElectrumRpcRequest, UnspentInfo, UtxoRpcClientEnum, UtxoRpcError, UtxoRpcFut, +use crate::utxo::rpc_clients::{ElectrumConnectionSettings, UnspentInfo, UtxoRpcClientEnum, UtxoRpcError, UtxoRpcFut, UtxoRpcResult}; use crate::utxo::utxo_builder::UtxoCoinBuildError; use crate::utxo::utxo_builder::{UtxoCoinBuilder, UtxoCoinBuilderCommonOps, UtxoFieldsWithGlobalHDBuilder, @@ -70,6 +70,7 @@ use serialization::CoinVariant; use std::collections::{HashMap, HashSet}; use std::convert::TryInto; use std::iter; +use std::num::TryFromIntError; use std::path::PathBuf; use std::sync::Arc; pub use z_coin_errors::*; @@ -106,7 +107,7 @@ cfg_wasm32!( use futures::channel::oneshot; use rand::rngs::OsRng; use zcash_primitives::transaction::builder::TransactionMetadata; - use z_coin_errors::ZCoinBalanceError; + pub use z_coin_errors::ZCoinBalanceError; ); /// `ZP2SHSpendError` compatible `TransactionErr` handling macro. @@ -138,7 +139,7 @@ cfg_native!( const SAPLING_OUTPUT_EXPECTED_HASH: &str = "2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4"; ); -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct ZcoinConsensusParams { // we don't support coins without overwinter and sapling active so these are mandatory overwinter_activation_height: u32, @@ -155,7 +156,7 @@ pub struct ZcoinConsensusParams { b58_script_address_prefix: [u8; 2], } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct CheckPointBlockInfo { height: u32, hash: H256Json, @@ -163,7 +164,7 @@ pub struct CheckPointBlockInfo { sapling_tree: BytesJson, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct ZcoinProtocolInfo { consensus_params: ZcoinConsensusParams, check_point_block: Option, @@ -750,7 +751,12 @@ pub enum ZcoinRpcMode { #[serde(alias = "Electrum")] Light { #[serde(alias = "servers")] - electrum_servers: Vec, + /// The settings of each electrum server. + electrum_servers: Vec, + /// The minimum number of connections to electrum servers to keep alive/maintained at all times. + min_connected: Option, + /// The maximum number of connections to electrum servers to not exceed at any time. + max_connected: Option, light_wallet_d_servers: Vec, /// Specifies the parameters for synchronizing the wallet from a specific block. This overrides the /// `CheckPointBlockInfo` configuration in the coin settings. @@ -967,8 +973,15 @@ impl<'a> ZCoinBuilder<'a> { let utxo_mode = match &z_coin_params.mode { #[cfg(not(target_arch = "wasm32"))] ZcoinRpcMode::Native => UtxoRpcMode::Native, - ZcoinRpcMode::Light { electrum_servers, .. } => UtxoRpcMode::Electrum { + ZcoinRpcMode::Light { + electrum_servers, + min_connected, + max_connected, + .. + } => UtxoRpcMode::Electrum { servers: electrum_servers.clone(), + min_connected: *min_connected, + max_connected: *max_connected, }, }; let utxo_params = UtxoActivationParams { @@ -1161,7 +1174,7 @@ impl MarketCoinOps for ZCoin { utxo_common::wait_for_confirmations(self.as_ref(), input) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { utxo_common::wait_for_output_spend( self.clone(), args.tx_bytes, @@ -1170,6 +1183,7 @@ impl MarketCoinOps for ZCoin { args.wait_until, args.check_every, ) + .await } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -1200,62 +1214,56 @@ impl MarketCoinOps for ZCoin { #[async_trait] impl SwapOps for ZCoin { - fn send_taker_fee(&self, _fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], _expire_at: u64) -> TransactionFut { - let selfi = self.clone(); + async fn send_taker_fee( + &self, + _fee_addr: &[u8], + dex_fee: DexFee, + uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { let uuid = uuid.to_owned(); - let fut = async move { - let tx = try_tx_s!(z_send_dex_fee(&selfi, dex_fee.fee_amount().into(), &uuid).await); - Ok(tx.into()) - }; - Box::new(fut.boxed().compat()) + let tx = try_tx_s!(z_send_dex_fee(self, dex_fee.fee_amount().into(), &uuid).await); + Ok(tx.into()) } - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { - let selfi = self.clone(); + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { let maker_key_pair = self.derive_htlc_key_pair(maker_payment_args.swap_unique_data); - let taker_pub = try_tx_fus!(Public::from_slice(maker_payment_args.other_pubkey)); + let taker_pub = try_tx_s!(Public::from_slice(maker_payment_args.other_pubkey)); let secret_hash = maker_payment_args.secret_hash.to_vec(); - let time_lock = try_tx_fus!(maker_payment_args.time_lock.try_into()); + let time_lock = try_tx_s!(maker_payment_args.time_lock.try_into()); let amount = maker_payment_args.amount; - let fut = async move { - let utxo_tx = try_tx_s!( - z_send_htlc( - &selfi, - time_lock, - maker_key_pair.public(), - &taker_pub, - &secret_hash, - amount - ) - .await - ); - Ok(utxo_tx.into()) - }; - Box::new(fut.boxed().compat()) + let utxo_tx = try_tx_s!( + z_send_htlc( + self, + time_lock, + maker_key_pair.public(), + &taker_pub, + &secret_hash, + amount + ) + .await + ); + Ok(utxo_tx.into()) } - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { - let selfi = self.clone(); + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { let taker_keypair = self.derive_htlc_key_pair(taker_payment_args.swap_unique_data); - let maker_pub = try_tx_fus!(Public::from_slice(taker_payment_args.other_pubkey)); + let maker_pub = try_tx_s!(Public::from_slice(taker_payment_args.other_pubkey)); let secret_hash = taker_payment_args.secret_hash.to_vec(); - let time_lock = try_tx_fus!(taker_payment_args.time_lock.try_into()); + let time_lock = try_tx_s!(taker_payment_args.time_lock.try_into()); let amount = taker_payment_args.amount; - let fut = async move { - let utxo_tx = try_tx_s!( - z_send_htlc( - &selfi, - time_lock, - taker_keypair.public(), - &maker_pub, - &secret_hash, - amount - ) - .await - ); - Ok(utxo_tx.into()) - }; - Box::new(fut.boxed().compat()) + let utxo_tx = try_tx_s!( + z_send_htlc( + self, + time_lock, + taker_keypair.public(), + &maker_pub, + &secret_hash, + amount + ) + .await + ); + Ok(utxo_tx.into()) } async fn send_maker_spends_taker_payment( @@ -1369,90 +1377,88 @@ impl SwapOps for ZCoin { Ok(tx.into()) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { let z_tx = match validate_fee_args.fee_tx { TransactionEnum::ZTransaction(t) => t.clone(), - _ => panic!("Unexpected tx {:?}", validate_fee_args.fee_tx), + fee_tx => { + return MmError::err(ValidatePaymentError::InternalError(format!( + "Invalid fee tx type. fee tx: {:?}", + fee_tx + ))) + }, }; - let amount_sat = try_f!(validate_fee_args.dex_fee.fee_uamount(self.utxo_arc.decimals)); + let amount_sat = validate_fee_args.dex_fee.fee_uamount(self.utxo_arc.decimals)?; let expected_memo = MemoBytes::from_bytes(validate_fee_args.uuid).expect("Uuid length < 512"); - let min_block_number = validate_fee_args.min_block_number; - let coin = self.clone(); - let fut = async move { - let tx_hash = H256::from(z_tx.txid().0).reversed(); - let tx_from_rpc = coin - .utxo_rpc_client() - .get_verbose_transaction(&tx_hash.into()) - .compat() - .await - .map_err(|e| MmError::new(ValidatePaymentError::InvalidRpcResponse(e.into_inner().to_string())))?; - - let mut encoded = Vec::with_capacity(1024); - z_tx.write(&mut encoded).expect("Writing should not fail"); - if encoded != tx_from_rpc.hex.0 { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Encoded transaction {:?} does not match the tx {:?} from RPC", - encoded, tx_from_rpc - ))); - } + let tx_hash = H256::from(z_tx.txid().0).reversed(); + let tx_from_rpc = self + .utxo_rpc_client() + .get_verbose_transaction(&tx_hash.into()) + .compat() + .await + .map_err(|e| MmError::new(ValidatePaymentError::InvalidRpcResponse(e.into_inner().to_string())))?; + + let mut encoded = Vec::with_capacity(1024); + z_tx.write(&mut encoded).expect("Writing should not fail"); + if encoded != tx_from_rpc.hex.0 { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Encoded transaction {:?} does not match the tx {:?} from RPC", + encoded, tx_from_rpc + ))); + } - let block_height = match tx_from_rpc.height { - Some(h) => { - if h < min_block_number { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Dex fee tx {:?} confirmed before min block {}", - z_tx, min_block_number - ))); - } else { - BlockHeight::from_u32(h as u32) - } - }, - None => H0, - }; - - for shielded_out in z_tx.shielded_outputs.iter() { - if let Some((note, address, memo)) = - try_sapling_output_recovery(coin.consensus_params_ref(), block_height, &DEX_FEE_OVK, shielded_out) - { - if address != coin.z_fields.dex_fee_addr { - let encoded = - encode_payment_address(z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS, &address); - let expected = encode_payment_address( - z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS, - &coin.z_fields.dex_fee_addr, - ); - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Dex fee was sent to the invalid address {}, expected {}", - encoded, expected - ))); - } - - if note.value != amount_sat { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Dex fee has invalid amount {}, expected {}", - note.value, amount_sat - ))); - } - - if memo != expected_memo { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Dex fee has invalid memo {:?}, expected {:?}", - memo, expected_memo - ))); - } - - return Ok(()); + let block_height = match tx_from_rpc.height { + Some(h) => { + if h < validate_fee_args.min_block_number { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Dex fee tx {:?} confirmed before min block {}", + z_tx, validate_fee_args.min_block_number + ))); + } else { + BlockHeight::from_u32(h as u32) } - } - - MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "The dex fee tx {:?} has no shielded outputs or outputs decryption failed", - z_tx - ))) + }, + None => H0, }; - Box::new(fut.boxed().compat()) + for shielded_out in z_tx.shielded_outputs.iter() { + if let Some((note, address, memo)) = + try_sapling_output_recovery(self.consensus_params_ref(), block_height, &DEX_FEE_OVK, shielded_out) + { + if address != self.z_fields.dex_fee_addr { + let encoded = encode_payment_address(z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS, &address); + let expected = encode_payment_address( + z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS, + &self.z_fields.dex_fee_addr, + ); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Dex fee was sent to the invalid address {}, expected {}", + encoded, expected + ))); + } + + if note.value != amount_sat { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Dex fee has invalid amount {}, expected {}", + note.value, amount_sat + ))); + } + + if memo != expected_memo { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Dex fee has invalid memo {:?}, expected {:?}", + memo, expected_memo + ))); + } + + return Ok(()); + } + } + + MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "The dex fee tx {:?} has no shielded outputs or outputs decryption failed", + z_tx + ))) } #[inline] @@ -1466,17 +1472,23 @@ impl SwapOps for ZCoin { } #[inline] - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, - ) -> Box, Error = String> + Send> { + ) -> Result, String> { + let time_lock = if_my_payment_sent_args + .time_lock + .try_into() + .map_err(|e: TryFromIntError| e.to_string())?; utxo_common::check_if_my_payment_sent( self.clone(), - try_fus!(if_my_payment_sent_args.time_lock.try_into()), + time_lock, if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, ) + .compat() + .await } #[inline] diff --git a/mm2src/coins/z_coin/storage.rs b/mm2src/coins/z_coin/storage.rs index 08e478f27a..b3c2c108c4 100644 --- a/mm2src/coins/z_coin/storage.rs +++ b/mm2src/coins/z_coin/storage.rs @@ -157,27 +157,8 @@ pub async fn scan_cached_block( ) }; - // Enforce that all roots match. This is slow, so only include in debug builds. - #[cfg(debug_assertions)] - { - let cur_root = tree.root(); - if witnesses.iter().any(|row| row.1.root() != cur_root) { - return Err(Error::InvalidWitnessAnchor(row.0, current_height).into()); - } - for tx in &txs { - for output in tx.shielded_outputs.iter() { - if output.witness.root() != cur_root { - return Err(Error::InvalidNewWitnessAnchor( - output.index, - tx.txid, - current_height, - output.witness.root(), - ) - .into()); - } - } - } - } + // To enforce that all roots match, + // see -> https://github.com/KomodoPlatform/librustzcash/blob/e92443a7bbd1c5e92e00e6deb45b5a33af14cea4/zcash_client_backend/src/data_api/chain.rs#L304-L326 let new_witnesses = data_guard .advance_by_block( diff --git a/mm2src/coins/z_coin/storage/blockdb/blockdb_sql_storage.rs b/mm2src/coins/z_coin/storage/blockdb/blockdb_sql_storage.rs index e8ce70d6dd..fea9a93277 100644 --- a/mm2src/coins/z_coin/storage/blockdb/blockdb_sql_storage.rs +++ b/mm2src/coins/z_coin/storage/blockdb/blockdb_sql_storage.rs @@ -44,15 +44,14 @@ impl From> for ZcoinStorageError { } impl BlockDbImpl { - #[cfg(all(not(test)))] + #[cfg(not(test))] pub async fn new(_ctx: &MmArc, ticker: String, path: PathBuf) -> ZcoinStorageRes { async_blocking(move || { let conn = Connection::open(path).map_to_mm(|err| ZcoinStorageError::DbError(err.to_string()))?; let conn = Arc::new(Mutex::new(conn)); - let conn_clone = conn.clone(); - let conn_clone = conn_clone.lock().unwrap(); - run_optimization_pragmas(&conn_clone).map_to_mm(|err| ZcoinStorageError::DbError(err.to_string()))?; - conn_clone + let conn_lock = conn.lock().unwrap(); + run_optimization_pragmas(&conn_lock).map_to_mm(|err| ZcoinStorageError::DbError(err.to_string()))?; + conn_lock .execute( "CREATE TABLE IF NOT EXISTS compactblocks ( height INTEGER PRIMARY KEY, @@ -61,23 +60,25 @@ impl BlockDbImpl { [], ) .map_to_mm(|err| ZcoinStorageError::DbError(err.to_string()))?; + drop(conn_lock); Ok(Self { db: conn, ticker }) }) .await } - #[cfg(all(test))] + #[cfg(test)] pub(crate) async fn new(ctx: &MmArc, ticker: String, _path: PathBuf) -> ZcoinStorageRes { let ctx = ctx.clone(); async_blocking(move || { let conn = ctx .sqlite_connection - .clone_or(Arc::new(Mutex::new(Connection::open_in_memory().unwrap()))); - let conn_clone = conn.clone(); - let conn_clone = conn_clone.lock().unwrap(); - run_optimization_pragmas(&conn_clone).map_err(|err| ZcoinStorageError::DbError(err.to_string()))?; - conn_clone + .get() + .cloned() + .unwrap_or_else(|| Arc::new(Mutex::new(Connection::open_in_memory().unwrap()))); + let conn_lock = conn.lock().unwrap(); + run_optimization_pragmas(&conn_lock).map_err(|err| ZcoinStorageError::DbError(err.to_string()))?; + conn_lock .execute( "CREATE TABLE IF NOT EXISTS compactblocks ( height INTEGER PRIMARY KEY, @@ -86,6 +87,7 @@ impl BlockDbImpl { [], ) .map_to_mm(|err| ZcoinStorageError::DbError(err.to_string()))?; + drop(conn_lock); Ok(BlockDbImpl { db: conn, ticker }) }) diff --git a/mm2src/coins/z_coin/storage/walletdb/wasm/mod.rs b/mm2src/coins/z_coin/storage/walletdb/wasm/mod.rs index ff35564385..4c68fec22c 100644 --- a/mm2src/coins/z_coin/storage/walletdb/wasm/mod.rs +++ b/mm2src/coins/z_coin/storage/walletdb/wasm/mod.rs @@ -37,7 +37,7 @@ fn to_spendable_note(note: SpendableNoteConstructor) -> MmResult().await?; - let earlist_block = block_headers_db + let earliest_block = block_headers_db .cursor_builder() .only("ticker", &self.ticker)? .bound("height", 0u32, u32::MAX) @@ -830,7 +830,7 @@ impl WalletRead for WalletIndexedDb { .next() .await?; - if let (Some(min), Some(max)) = (earlist_block, latest_block) { + if let (Some(min), Some(max)) = (earliest_block, latest_block) { Ok(Some((BlockHeight::from(min.1.height), BlockHeight::from(max.1.height)))) } else { Ok(None) diff --git a/mm2src/coins/z_coin/z_coin_native_tests.rs b/mm2src/coins/z_coin/z_coin_native_tests.rs index 9e4358727d..f554d7c5d3 100644 --- a/mm2src/coins/z_coin/z_coin_native_tests.rs +++ b/mm2src/coins/z_coin/z_coin_native_tests.rs @@ -6,9 +6,8 @@ use std::path::PathBuf; use std::time::Duration; use zcash_client_backend::encoding::decode_extended_spending_key; -use super::{z_coin_from_conf_and_params_with_z_key, z_mainnet_constants, Future, PrivKeyBuildPolicy, - RefundPaymentArgs, SendPaymentArgs, SpendPaymentArgs, SwapOps, ValidateFeeArgs, ValidatePaymentError, - ZTransaction}; +use super::{z_coin_from_conf_and_params_with_z_key, z_mainnet_constants, PrivKeyBuildPolicy, RefundPaymentArgs, + SendPaymentArgs, SpendPaymentArgs, SwapOps, ValidateFeeArgs, ValidatePaymentError, ZTransaction}; use crate::z_coin::{z_htlc::z_send_dex_fee, ZcoinActivationParams, ZcoinRpcMode}; use crate::DexFee; use crate::{CoinProtocol, SwapTxTypeWithSecretHash}; @@ -55,7 +54,7 @@ fn zombie_coin_send_and_refund_maker_payment() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_maker_payment(args).wait().unwrap(); + let tx = block_on(coin.send_maker_payment(args)).unwrap(); log!("swap tx {}", hex::encode(tx.tx_hash_as_bytes().0)); let refund_args = RefundPaymentArgs { @@ -116,7 +115,7 @@ fn zombie_coin_send_and_spend_maker_payment() { wait_for_confirmation_until: 0, }; - let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let tx = block_on(coin.send_maker_payment(maker_payment_args)).unwrap(); log!("swap tx {}", hex::encode(tx.tx_hash_as_bytes().0)); let maker_pub = taker_pub; @@ -234,7 +233,7 @@ fn zombie_coin_validate_dex_fee() { uuid: &[1; 16], }; // Invalid amount should return an error - let err = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let err = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match err { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Dex fee has invalid amount")), _ => panic!("Expected `WrongPaymentTx`: {:?}", err), @@ -249,7 +248,7 @@ fn zombie_coin_validate_dex_fee() { min_block_number: 12000, uuid: &[2; 16], }; - let err = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let err = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match err { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Dex fee has invalid memo")), _ => panic!("Expected `WrongPaymentTx`: {:?}", err), @@ -264,7 +263,7 @@ fn zombie_coin_validate_dex_fee() { min_block_number: 14000, uuid: &[1; 16], }; - let err = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let err = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match err { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min block")), _ => panic!("Expected `WrongPaymentTx`: {:?}", err), @@ -279,7 +278,7 @@ fn zombie_coin_validate_dex_fee() { min_block_number: 12000, uuid: &[1; 16], }; - coin.validate_fee(validate_fee_args).wait().unwrap(); + block_on(coin.validate_fee(validate_fee_args)).unwrap(); } fn default_zcoin_activation_params() -> ZcoinActivationParams { diff --git a/mm2src/coins_activation/Cargo.toml b/mm2src/coins_activation/Cargo.toml index aef4b9b557..be951c67d4 100644 --- a/mm2src/coins_activation/Cargo.toml +++ b/mm2src/coins_activation/Cargo.toml @@ -8,7 +8,6 @@ doctest = false [features] enable-sia = [] -enable-solana = [] default = [] for-tests = [] diff --git a/mm2src/coins_activation/src/erc20_token_activation.rs b/mm2src/coins_activation/src/erc20_token_activation.rs index 664f2c22fd..77970284b4 100644 --- a/mm2src/coins_activation/src/erc20_token_activation.rs +++ b/mm2src/coins_activation/src/erc20_token_activation.rs @@ -10,6 +10,7 @@ use coins::{eth::{v2_activation::{Erc20Protocol, EthTokenActivationError}, use common::Future01CompatExt; use mm2_err_handle::prelude::*; use serde::Serialize; +use serde_json::Value as Json; use std::collections::HashMap; #[derive(Debug, Serialize)] @@ -42,6 +43,8 @@ impl From for EnableTokenError { | EthTokenActivationError::ClientConnectionFailed(e) => EnableTokenError::Transport(e), EthTokenActivationError::InvalidPayload(e) => EnableTokenError::InvalidPayload(e), EthTokenActivationError::UnexpectedDerivationMethod(e) => EnableTokenError::UnexpectedDerivationMethod(e), + EthTokenActivationError::PrivKeyPolicyNotAllowed(e) => EnableTokenError::PrivKeyPolicyNotAllowed(e), + EthTokenActivationError::CustomTokenError(e) => EnableTokenError::CustomTokenError(e), } } } @@ -80,6 +83,18 @@ impl TryFromCoinProtocol for Erc20Protocol { } } +impl TryFromCoinProtocol for NftProtocol { + fn try_from_coin_protocol(proto: CoinProtocol) -> Result> + where + Self: Sized, + { + match proto { + CoinProtocol::NFT { platform } => Ok(NftProtocol { platform }), + proto => MmError::err(proto), + } + } +} + impl TryFromCoinProtocol for EthTokenProtocol { fn try_from_coin_protocol(proto: CoinProtocol) -> Result> where @@ -120,13 +135,21 @@ impl TokenActivationOps for EthCoin { ticker: String, platform_coin: Self::PlatformCoin, activation_params: Self::ActivationParams, + token_conf: Json, protocol_conf: Self::ProtocolInfo, + is_custom: bool, ) -> Result<(Self, Self::ActivationResult), MmError> { match activation_params { EthTokenActivationParams::Erc20(erc20_init_params) => match protocol_conf { EthTokenProtocol::Erc20(erc20_protocol) => { let token = platform_coin - .initialize_erc20_token(erc20_init_params, erc20_protocol, ticker.clone()) + .initialize_erc20_token( + ticker.clone(), + erc20_init_params, + token_conf, + erc20_protocol, + is_custom, + ) .await?; let address = display_eth_address(&token.derivation_method().single_addr_or_err().await?); @@ -163,7 +186,9 @@ impl TokenActivationOps for EthCoin { )); } let nft_global = match &nft_init_params.provider { - NftProviderEnum::Moralis { url } => platform_coin.global_nft_from_platform_coin(url).await?, + NftProviderEnum::Moralis { url, komodo_proxy } => { + platform_coin.initialize_global_nft(url, *komodo_proxy).await? + }, }; let nfts = nft_global.nfts_infos.lock().await.clone(); let init_result = EthTokenInitResult::Nft(NftInitResult { diff --git a/mm2src/coins_activation/src/eth_with_token_activation.rs b/mm2src/coins_activation/src/eth_with_token_activation.rs index 296cfcfd73..7bc62b444a 100644 --- a/mm2src/coins_activation/src/eth_with_token_activation.rs +++ b/mm2src/coins_activation/src/eth_with_token_activation.rs @@ -12,7 +12,7 @@ use coins::coin_balance::{CoinBalanceReport, EnableCoinBalanceOps}; use coins::eth::v2_activation::{eth_coin_from_conf_and_request_v2, Erc20Protocol, Erc20TokenActivationRequest, EthActivationV2Error, EthActivationV2Request, EthPrivKeyActivationPolicy}; use coins::eth::v2_activation::{EthTokenActivationError, NftActivationRequest, NftProviderEnum}; -use coins::eth::{Erc20TokenInfo, EthCoin, EthCoinType, EthPrivKeyBuildPolicy}; +use coins::eth::{display_eth_address, Erc20TokenDetails, EthCoin, EthCoinType, EthPrivKeyBuildPolicy}; use coins::hd_wallet::RpcTaskXPubExtractor; use coins::my_tx_history_v2::TxHistoryStorage; use coins::nft::nft_structs::NftInfo; @@ -85,6 +85,7 @@ impl From for EnablePlatformCoinWithTokensError { EthActivationV2Error::InvalidHardwareWalletCall => EnablePlatformCoinWithTokensError::Internal( "Hardware wallet must be used within rpc task manager".to_string(), ), + EthActivationV2Error::CustomTokenError(e) => EnablePlatformCoinWithTokensError::CustomTokenError(e), } } } @@ -117,6 +118,8 @@ impl From for InitTokensAsMmCoinsError { EthTokenActivationError::UnexpectedDerivationMethod(e) => { InitTokensAsMmCoinsError::UnexpectedDerivationMethod(e) }, + EthTokenActivationError::PrivKeyPolicyNotAllowed(e) => InitTokensAsMmCoinsError::Internal(e.to_string()), + EthTokenActivationError::CustomTokenError(e) => InitTokensAsMmCoinsError::CustomTokenError(e), } } } @@ -142,7 +145,13 @@ impl TokenInitializer for Erc20Initializer { for param in activation_params { let token: EthCoin = self .platform_coin - .initialize_erc20_token(param.activation_request, param.protocol, param.ticker) + .initialize_erc20_token( + param.ticker, + param.activation_request, + param.conf, + param.protocol, + param.is_custom, + ) .await?; tokens.push(token); } @@ -182,7 +191,7 @@ impl RegisterTokenInfo for EthCoin { return; } - self.add_erc_token_info(token.ticker().to_string(), Erc20TokenInfo { + self.add_erc_token_info(token.ticker().to_string(), Erc20TokenDetails { token_address: token.erc20_token_address().unwrap(), decimals: token.decimals(), }); @@ -288,13 +297,13 @@ impl PlatformCoinWithTokensActivationOps for EthCoin { &self, activation_request: &Self::ActivationRequest, ) -> Result, MmError> { - let url = match &activation_request.nft_req { + let (url, proxy_auth) = match &activation_request.nft_req { Some(nft_req) => match &nft_req.provider { - NftProviderEnum::Moralis { url } => url, + NftProviderEnum::Moralis { url, komodo_proxy } => (url, *komodo_proxy), }, None => return Ok(None), }; - let nft_global = self.global_nft_from_platform_coin(url).await?; + let nft_global = self.initialize_global_nft(url, proxy_auth).await?; Ok(Some(MmCoinEnum::EthCoin(nft_global))) } @@ -359,8 +368,11 @@ impl PlatformCoinWithTokensActivationOps for EthCoin { return Ok(EthWithTokensActivationResult::Iguana( IguanaEthWithTokensActivationResult { current_block, - eth_addresses_infos: HashMap::from([(my_address.to_string(), eth_address_info)]), - erc20_addresses_infos: HashMap::from([(my_address.to_string(), erc20_address_info)]), + eth_addresses_infos: HashMap::from([(display_eth_address(my_address), eth_address_info)]), + erc20_addresses_infos: HashMap::from([( + display_eth_address(my_address), + erc20_address_info, + )]), nfts_infos: nfts_map, }, )); @@ -384,8 +396,8 @@ impl PlatformCoinWithTokensActivationOps for EthCoin { Ok(EthWithTokensActivationResult::Iguana( IguanaEthWithTokensActivationResult { current_block, - eth_addresses_infos: HashMap::from([(my_address.to_string(), eth_address_info)]), - erc20_addresses_infos: HashMap::from([(my_address.to_string(), erc20_address_info)]), + eth_addresses_infos: HashMap::from([(display_eth_address(my_address), eth_address_info)]), + erc20_addresses_infos: HashMap::from([(display_eth_address(my_address), erc20_address_info)]), nfts_infos: nfts_map, }, )) diff --git a/mm2src/coins_activation/src/init_erc20_token_activation.rs b/mm2src/coins_activation/src/init_erc20_token_activation.rs index 5bc4e665ff..f162cb1754 100644 --- a/mm2src/coins_activation/src/init_erc20_token_activation.rs +++ b/mm2src/coins_activation/src/init_erc20_token_activation.rs @@ -7,7 +7,7 @@ use coins::coin_balance::{EnableCoinBalanceError, EnableCoinBalanceOps}; use coins::eth::v2_activation::{Erc20Protocol, EthTokenActivationError, InitErc20TokenActivationRequest}; use coins::eth::EthCoin; use coins::hd_wallet::RpcTaskXPubExtractor; -use coins::{MarketCoinOps, MmCoin, RegisterCoinError}; +use coins::{CustomTokenError, MarketCoinOps, MmCoin, RegisterCoinError}; use common::Future01CompatExt; use crypto::HwRpcError; use derive_more::Display; @@ -17,6 +17,7 @@ use mm2_err_handle::prelude::*; use rpc_task::RpcTaskError; use ser_error_derive::SerializeErrorType; use serde_derive::Serialize; +use serde_json::Value as Json; use std::time::Duration; pub type Erc20TokenTaskManagerShared = InitTokenTaskManagerShared; @@ -38,6 +39,8 @@ pub enum InitErc20Error { Transport(String), #[display(fmt = "Internal error: {}", _0)] Internal(String), + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(CustomTokenError), } impl From for InitTokenError { @@ -45,13 +48,16 @@ impl From for InitTokenError { match e { InitErc20Error::HwError(hw) => InitTokenError::HwError(hw), InitErc20Error::TaskTimedOut { duration } => InitTokenError::TaskTimedOut { duration }, - InitErc20Error::TokenIsAlreadyActivated { ticker } => InitTokenError::TokenIsAlreadyActivated { ticker }, + InitErc20Error::TokenIsAlreadyActivated { ticker, .. } => { + InitTokenError::TokenIsAlreadyActivated { ticker } + }, InitErc20Error::TokenCreationError { ticker, error } => { InitTokenError::TokenCreationError { ticker, error } }, InitErc20Error::CouldNotFetchBalance(error) => InitTokenError::CouldNotFetchBalance(error), InitErc20Error::Transport(transport) => InitTokenError::Transport(transport), InitErc20Error::Internal(internal) => InitTokenError::Internal(internal), + InitErc20Error::CustomTokenError(error) => InitTokenError::CustomTokenError(error), } } } @@ -59,13 +65,14 @@ impl From for InitTokenError { impl From for InitErc20Error { fn from(e: EthTokenActivationError) -> Self { match e { - EthTokenActivationError::InternalError(_) | EthTokenActivationError::UnexpectedDerivationMethod(_) => { - InitErc20Error::Internal(e.to_string()) - }, + EthTokenActivationError::InternalError(_) + | EthTokenActivationError::UnexpectedDerivationMethod(_) + | EthTokenActivationError::PrivKeyPolicyNotAllowed(_) => InitErc20Error::Internal(e.to_string()), EthTokenActivationError::ClientConnectionFailed(_) | EthTokenActivationError::CouldNotFetchBalance(_) | EthTokenActivationError::InvalidPayload(_) | EthTokenActivationError::Transport(_) => InitErc20Error::Transport(e.to_string()), + EthTokenActivationError::CustomTokenError(e) => InitErc20Error::CustomTokenError(e), } } } @@ -118,11 +125,19 @@ impl InitTokenActivationOps for EthCoin { ticker: String, platform_coin: Self::PlatformCoin, activation_request: &Self::ActivationRequest, + token_conf: Json, protocol_conf: Self::ProtocolInfo, _task_handle: InitTokenTaskHandleShared, + is_custom: bool, ) -> Result> { let token = platform_coin - .initialize_erc20_token(activation_request.clone().into(), protocol_conf, ticker) + .initialize_erc20_token( + ticker, + activation_request.clone().into(), + token_conf, + protocol_conf, + is_custom, + ) .await?; Ok(token) diff --git a/mm2src/coins_activation/src/init_token.rs b/mm2src/coins_activation/src/init_token.rs index dbc03b1754..01d47b3656 100644 --- a/mm2src/coins_activation/src/init_token.rs +++ b/mm2src/coins_activation/src/init_token.rs @@ -5,7 +5,8 @@ use crate::prelude::{coin_conf_with_protocol, CoinConfWithProtocolError, Current use crate::token::TokenProtocolParams; use async_trait::async_trait; use coins::coin_balance::CoinBalanceReport; -use coins::{lp_coinfind, lp_coinfind_or_err, CoinBalanceMap, CoinProtocol, CoinsContext, MmCoinEnum, RegisterCoinError}; +use coins::{lp_coinfind, lp_coinfind_or_err, CoinBalanceMap, CoinProtocol, CoinsContext, CustomTokenError, MmCoinEnum, + RegisterCoinError}; use common::{log, HttpStatusCode, StatusCode, SuccessResponse}; use crypto::hw_rpc_task::{HwConnectStatuses, HwRpcTaskAwaitingStatus, HwRpcTaskUserAction}; use crypto::HwRpcError; @@ -19,6 +20,7 @@ use rpc_task::{RpcTask, RpcTaskError, RpcTaskHandleShared, RpcTaskManager, RpcTa RpcTaskTypes, TaskId}; use ser_error_derive::SerializeErrorType; use serde_derive::{Deserialize, Serialize}; +use serde_json::Value as Json; use std::time::Duration; pub type InitTokenResponse = InitRpcTaskResponse; @@ -37,6 +39,7 @@ pub type CancelInitTokenError = CancelRpcTaskError; #[derive(Debug, Deserialize, Clone)] pub struct InitTokenReq { ticker: String, + protocol: Option, activation_params: T, } @@ -65,8 +68,10 @@ pub trait InitTokenActivationOps: Into + TokenOf + Clone + Send + Sy ticker: String, platform_coin: Self::PlatformCoin, activation_request: &Self::ActivationRequest, + token_conf: Json, protocol_conf: Self::ProtocolInfo, task_handle: InitTokenTaskHandleShared, + is_custom: bool, ) -> Result>; /// Returns the result of the token activation. @@ -94,7 +99,8 @@ where return MmError::err(InitTokenError::TokenIsAlreadyActivated { ticker: request.ticker }); } - let (_, token_protocol): (_, Token::ProtocolInfo) = coin_conf_with_protocol(&ctx, &request.ticker)?; + let (token_conf, token_protocol): (_, Token::ProtocolInfo) = + coin_conf_with_protocol(&ctx, &request.ticker, request.protocol.clone())?; let platform_coin = lp_coinfind_or_err(&ctx, token_protocol.platform_coin_ticker()) .await @@ -111,6 +117,7 @@ where let task = InitTokenTask:: { ctx, request, + token_conf, token_protocol, platform_coin, }; @@ -174,6 +181,7 @@ pub async fn cancel_init_token( pub struct InitTokenTask { ctx: MmArc, request: InitTokenReq, + token_conf: Json, token_protocol: Token::ProtocolInfo, platform_coin: Token::PlatformCoin, } @@ -210,8 +218,10 @@ where ticker.clone(), self.platform_coin.clone(), &self.request.activation_params, + self.token_conf.clone(), self.token_protocol.clone(), task_handle.clone(), + self.request.protocol.is_some(), ) .await?; @@ -297,8 +307,8 @@ pub enum InitTokenError { TokenConfigIsNotFound(String), #[display(fmt = "Token {} protocol parsing failed: {}", ticker, error)] TokenProtocolParseError { ticker: String, error: String }, - #[display(fmt = "Unexpected platform protocol {:?} for {}", protocol, ticker)] - UnexpectedTokenProtocol { ticker: String, protocol: CoinProtocol }, + #[display(fmt = "Unexpected platform protocol {} for {}", protocol, ticker)] + UnexpectedTokenProtocol { ticker: String, protocol: Json }, #[display(fmt = "Error on platform coin {} creation: {}", ticker, error)] TokenCreationError { ticker: String, error: String }, #[display(fmt = "Could not fetch balance: {}", _0)] @@ -310,6 +320,8 @@ pub enum InitTokenError { platform_coin_ticker: String, token_ticker: String, }, + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(CustomTokenError), #[display(fmt = "{}", _0)] HwError(HwRpcError), #[display(fmt = "Transport error: {}", _0)] @@ -331,6 +343,7 @@ impl From for InitTokenError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitTokenError::UnexpectedTokenProtocol { ticker, protocol } }, + CoinConfWithProtocolError::CustomTokenError(e) => InitTokenError::CustomTokenError(e), } } } @@ -354,7 +367,8 @@ impl HttpStatusCode for InitTokenError { | InitTokenError::TokenProtocolParseError { .. } | InitTokenError::UnexpectedTokenProtocol { .. } | InitTokenError::TokenCreationError { .. } - | InitTokenError::PlatformCoinIsNotActivated(_) => StatusCode::BAD_REQUEST, + | InitTokenError::PlatformCoinIsNotActivated(_) + | InitTokenError::CustomTokenError(_) => StatusCode::BAD_REQUEST, InitTokenError::TaskTimedOut { .. } => StatusCode::REQUEST_TIMEOUT, InitTokenError::HwError(_) => StatusCode::GONE, InitTokenError::CouldNotFetchBalance(_) diff --git a/mm2src/coins_activation/src/l2/init_l2.rs b/mm2src/coins_activation/src/l2/init_l2.rs index 20e66ebbde..e6b0888700 100644 --- a/mm2src/coins_activation/src/l2/init_l2.rs +++ b/mm2src/coins_activation/src/l2/init_l2.rs @@ -79,7 +79,7 @@ where return MmError::err(InitL2Error::L2IsAlreadyActivated(ticker)); } - let (coin_conf_json, protocol_conf): (Json, L2::ProtocolInfo) = coin_conf_with_protocol(&ctx, &ticker)?; + let (coin_conf_json, protocol_conf): (Json, L2::ProtocolInfo) = coin_conf_with_protocol(&ctx, &ticker, None)?; let coin_conf = L2::coin_conf_from_json(coin_conf_json)?; let platform_coin = lp_coinfind_or_err(&ctx, protocol_conf.platform_coin_ticker()) diff --git a/mm2src/coins_activation/src/l2/init_l2_error.rs b/mm2src/coins_activation/src/l2/init_l2_error.rs index d23fd73078..71eb57fd23 100644 --- a/mm2src/coins_activation/src/l2/init_l2_error.rs +++ b/mm2src/coins_activation/src/l2/init_l2_error.rs @@ -1,11 +1,11 @@ use crate::prelude::CoinConfWithProtocolError; -use coins::CoinProtocol; use common::{HttpStatusCode, StatusCode}; use derive_more::Display; use rpc_task::rpc_common::{CancelRpcTaskError, RpcTaskStatusError, RpcTaskUserActionError}; use rpc_task::RpcTaskError; use ser_error_derive::SerializeErrorType; use serde_derive::Serialize; +use serde_json::Value as Json; use std::time::Duration; pub type InitL2StatusError = RpcTaskStatusError; @@ -24,10 +24,10 @@ pub enum InitL2Error { ticker: String, error: String, }, - #[display(fmt = "Unexpected layer 2 protocol {:?} for {}", protocol, ticker)] + #[display(fmt = "Unexpected layer 2 protocol {} for {}", protocol, ticker)] UnexpectedL2Protocol { ticker: String, - protocol: CoinProtocol, + protocol: Json, }, #[display(fmt = "Platform coin {} is not activated", _0)] PlatformCoinIsNotActivated(String), @@ -62,6 +62,9 @@ impl From for InitL2Error { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitL2Error::UnexpectedL2Protocol { ticker, protocol } }, + CoinConfWithProtocolError::CustomTokenError(e) => { + InitL2Error::Internal(format!("Custom tokens are not supported for L2: {}", e)) + }, } } } diff --git a/mm2src/coins_activation/src/lib.rs b/mm2src/coins_activation/src/lib.rs index 8a779b80f4..8020f91f27 100644 --- a/mm2src/coins_activation/src/lib.rs +++ b/mm2src/coins_activation/src/lib.rs @@ -10,20 +10,6 @@ mod platform_coin_with_tokens; mod prelude; #[cfg(feature = "enable-sia")] mod sia_coin_activation; mod slp_token_activation; -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -mod solana_with_tokens_activation; -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -mod spl_token_activation; mod standalone_coin; mod tendermint_token_activation; mod tendermint_with_assets_activation; diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index 051dd22fc3..d5ee5cfbf0 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -5,8 +5,8 @@ use crate::prelude::*; use async_trait::async_trait; use coins::my_tx_history_v2::TxHistoryStorage; use coins::tx_history_storage::{CreateTxHistoryStorageError, TxHistoryStorageBuilder}; -use coins::{lp_coinfind, lp_coinfind_any, CoinProtocol, CoinsContext, MmCoinEnum, PrivKeyPolicyNotAllowed, - UnexpectedDerivationMethod}; +use coins::{lp_coinfind, lp_coinfind_any, CoinProtocol, CoinsContext, CustomTokenError, MmCoinEnum, + PrivKeyPolicyNotAllowed, UnexpectedDerivationMethod}; use common::{log, HttpStatusCode, StatusCode, SuccessResponse}; use crypto::hw_rpc_task::{HwConnectStatuses, HwRpcTaskAwaitingStatus, HwRpcTaskUserAction}; use crypto::CryptoCtxError; @@ -38,6 +38,7 @@ pub type InitPlatformCoinWithTokensTaskManagerShared = #[derive(Clone, Debug, Deserialize)] pub struct TokenActivationRequest { ticker: String, + protocol: Option, #[serde(flatten)] request: Req, } @@ -51,8 +52,10 @@ pub trait TokenOf: Into { pub struct TokenActivationParams { pub(crate) ticker: String, + pub(crate) conf: Json, pub(crate) activation_request: Req, pub(crate) protocol: Protocol, + pub(crate) is_custom: bool, } #[async_trait] @@ -87,15 +90,15 @@ pub trait TokenAsMmCoinInitializer: Send + Sync { } pub enum InitTokensAsMmCoinsError { - TokenAlreadyActivated(String), TokenConfigIsNotFound(String), CouldNotFetchBalance(String), UnexpectedDerivationMethod(UnexpectedDerivationMethod), Internal(String), TokenProtocolParseError { ticker: String, error: String }, - UnexpectedTokenProtocol { ticker: String, protocol: CoinProtocol }, + UnexpectedTokenProtocol { ticker: String, protocol: Json }, Transport(String), InvalidPayload(String), + CustomTokenError(CustomTokenError), } impl From for InitTokensAsMmCoinsError { @@ -111,6 +114,7 @@ impl From for InitTokensAsMmCoinsError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitTokensAsMmCoinsError::UnexpectedTokenProtocol { ticker, protocol } }, + CoinConfWithProtocolError::CustomTokenError(e) => InitTokensAsMmCoinsError::CustomTokenError(e), } } } @@ -138,11 +142,14 @@ where let token_params = tokens_requests .into_iter() .map(|req| -> Result<_, MmError> { - let (_, protocol): (_, T::TokenProtocol) = coin_conf_with_protocol(&ctx, &req.ticker)?; + let (token_conf, protocol): (_, T::TokenProtocol) = + coin_conf_with_protocol(&ctx, &req.ticker, req.protocol.clone())?; Ok(TokenActivationParams { ticker: req.ticker, + conf: token_conf, activation_request: req.request, protocol, + is_custom: req.protocol.is_some(), }) }) .collect::, _>>()?; @@ -235,7 +242,6 @@ pub struct EnablePlatformCoinWithTokensReq { #[serde(tag = "error_type", content = "error_data")] pub enum EnablePlatformCoinWithTokensError { PlatformIsAlreadyActivated(String), - TokenIsAlreadyActivated(String), #[display(fmt = "Platform {} config is not found", _0)] PlatformConfigIsNotFound(String), #[display(fmt = "Platform coin {} protocol parsing failed: {}", ticker, error)] @@ -243,10 +249,10 @@ pub enum EnablePlatformCoinWithTokensError { ticker: String, error: String, }, - #[display(fmt = "Unexpected platform protocol {:?} for {}", protocol, ticker)] + #[display(fmt = "Unexpected platform protocol {} for {}", protocol, ticker)] UnexpectedPlatformProtocol { ticker: String, - protocol: CoinProtocol, + protocol: Json, }, #[display(fmt = "Token {} config is not found", _0)] TokenConfigIsNotFound(String), @@ -255,10 +261,10 @@ pub enum EnablePlatformCoinWithTokensError { ticker: String, error: String, }, - #[display(fmt = "Unexpected token protocol {:?} for {}", protocol, ticker)] + #[display(fmt = "Unexpected token protocol {} for {}", protocol, ticker)] UnexpectedTokenProtocol { ticker: String, - protocol: CoinProtocol, + protocol: Json, }, #[display(fmt = "Error on platform coin {} creation: {}", ticker, error)] PlatformCoinCreationError { @@ -283,6 +289,8 @@ pub enum EnablePlatformCoinWithTokensError { }, #[display(fmt = "Hardware policy must be activated within task manager")] UnexpectedDeviceActivationPolicy, + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(CustomTokenError), } impl From for EnablePlatformCoinWithTokensError { @@ -300,6 +308,7 @@ impl From for EnablePlatformCoinWithTokensError { error: err.to_string(), } }, + CoinConfWithProtocolError::CustomTokenError(e) => EnablePlatformCoinWithTokensError::CustomTokenError(e), } } } @@ -307,9 +316,6 @@ impl From for EnablePlatformCoinWithTokensError { impl From for EnablePlatformCoinWithTokensError { fn from(err: InitTokensAsMmCoinsError) -> Self { match err { - InitTokensAsMmCoinsError::TokenAlreadyActivated(ticker) => { - EnablePlatformCoinWithTokensError::TokenIsAlreadyActivated(ticker) - }, InitTokensAsMmCoinsError::TokenConfigIsNotFound(ticker) => { EnablePlatformCoinWithTokensError::TokenConfigIsNotFound(ticker) }, @@ -327,6 +333,7 @@ impl From for EnablePlatformCoinWithTokensError { InitTokensAsMmCoinsError::UnexpectedDerivationMethod(e) => { EnablePlatformCoinWithTokensError::UnexpectedDerivationMethod(e.to_string()) }, + InitTokensAsMmCoinsError::CustomTokenError(e) => EnablePlatformCoinWithTokensError::CustomTokenError(e), } } } @@ -362,9 +369,9 @@ impl HttpStatusCode for EnablePlatformCoinWithTokensError { | EnablePlatformCoinWithTokensError::PrivKeyPolicyNotAllowed(_) | EnablePlatformCoinWithTokensError::UnexpectedDerivationMethod(_) | EnablePlatformCoinWithTokensError::Internal(_) - | EnablePlatformCoinWithTokensError::TaskTimedOut { .. } => StatusCode::INTERNAL_SERVER_ERROR, + | EnablePlatformCoinWithTokensError::TaskTimedOut { .. } + | EnablePlatformCoinWithTokensError::CustomTokenError(_) => StatusCode::INTERNAL_SERVER_ERROR, EnablePlatformCoinWithTokensError::PlatformIsAlreadyActivated(_) - | EnablePlatformCoinWithTokensError::TokenIsAlreadyActivated(_) | EnablePlatformCoinWithTokensError::PlatformConfigIsNotFound(_) | EnablePlatformCoinWithTokensError::TokenConfigIsNotFound(_) | EnablePlatformCoinWithTokensError::UnexpectedPlatformProtocol { .. } @@ -449,7 +456,7 @@ where )); } - let (platform_conf, platform_protocol) = coin_conf_with_protocol(&ctx, &req.ticker)?; + let (platform_conf, platform_protocol) = coin_conf_with_protocol(&ctx, &req.ticker, None)?; let platform_coin = Platform::enable_platform_coin( ctx.clone(), diff --git a/mm2src/coins_activation/src/prelude.rs b/mm2src/coins_activation/src/prelude.rs index 36fe5aa43b..42c93c1377 100644 --- a/mm2src/coins_activation/src/prelude.rs +++ b/mm2src/coins_activation/src/prelude.rs @@ -1,14 +1,13 @@ -use coins::nft::nft_structs::{Chain, ConvertChain}; #[cfg(feature = "enable-sia")] -use coins::sia::SiaCoinActivationParams; +use coins::siacoin::SiaCoinActivationParams; use coins::utxo::UtxoActivationParams; use coins::z_coin::ZcoinActivationParams; -use coins::{coin_conf, CoinBalance, CoinProtocol, DerivationMethodResponse, MmCoinEnum}; +use coins::{coin_conf, CoinBalance, CoinProtocol, CustomTokenError, DerivationMethodResponse, MmCoinEnum}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_number::BigDecimal; use serde_derive::Serialize; -use serde_json::{self as json, Value as Json}; +use serde_json::{self as json, json, Value as Json}; use std::collections::{HashMap, HashSet}; pub trait CurrentBlock { @@ -65,47 +64,79 @@ pub trait TryFromCoinProtocol { pub enum CoinConfWithProtocolError { ConfigIsNotFound(String), CoinProtocolParseError { ticker: String, err: json::Error }, - UnexpectedProtocol { ticker: String, protocol: CoinProtocol }, + UnexpectedProtocol { ticker: String, protocol: Json }, + CustomTokenError(CustomTokenError), } /// Determines the coin configuration and protocol information for a given coin or NFT ticker. -/// In the case of NFT ticker, it's platform coin config will be returned. -#[allow(clippy::result_large_err)] pub fn coin_conf_with_protocol( ctx: &MmArc, coin: &str, + protocol_from_request: Option, ) -> Result<(Json, T), MmError> { - let (conf, coin_protocol) = match Chain::from_nft_ticker(coin) { - Ok(chain) => { - let platform = chain.to_ticker(); - let platform_conf = coin_conf(ctx, platform); - let nft_protocol = CoinProtocol::NFT { - platform: platform.to_string(), - }; - (platform_conf, nft_protocol) - }, - Err(_) => { - let conf = coin_conf(ctx, coin); - let coin_protocol: CoinProtocol = json::from_value(conf["protocol"].clone()).map_to_mm(|err| { - CoinConfWithProtocolError::CoinProtocolParseError { - ticker: coin.into(), - err, - } - })?; - (conf, coin_protocol) - }, - }; - - if conf.is_null() { - return MmError::err(CoinConfWithProtocolError::ConfigIsNotFound(coin.into())); + let conf = coin_conf(ctx, coin); + let is_ticker_in_config = !conf.is_null(); + + // For `protocol_from_request`: None = config-based activation, Some = custom token activation + match (protocol_from_request, is_ticker_in_config) { + // Config-based activation requested with an existing configuration + // Proceed with parsing protocol info from config + (None, true) => parse_coin_protocol_from_config(conf, coin), + // Custom token activation requested and no matching ticker in config + // Proceed with custom token config creation from protocol info + (Some(protocol), false) => create_custom_token_config(ctx, coin, protocol), + // Custom token activation requested but a coin with the same ticker already exists in config + (Some(_), true) => Err(MmError::new(CoinConfWithProtocolError::CustomTokenError( + CustomTokenError::DuplicateTickerInConfig { + ticker_in_config: coin.to_string(), + }, + ))), + // Config-based activation requested but ticker not found in config + (None, false) => Err(MmError::new(CoinConfWithProtocolError::ConfigIsNotFound(coin.into()))), } +} + +fn parse_coin_protocol_from_config( + conf: Json, + coin: &str, +) -> Result<(Json, T), MmError> { + let protocol = json::from_value(conf["protocol"].clone()).map_to_mm(|err| { + CoinConfWithProtocolError::CoinProtocolParseError { + ticker: coin.into(), + err, + } + })?; + + let coin_protocol = + T::try_from_coin_protocol(protocol).mm_err(|p| CoinConfWithProtocolError::UnexpectedProtocol { + ticker: coin.into(), + protocol: json!(p), + })?; - let protocol = - T::try_from_coin_protocol(coin_protocol).mm_err(|protocol| CoinConfWithProtocolError::UnexpectedProtocol { + Ok((conf, coin_protocol)) +} + +fn create_custom_token_config( + ctx: &MmArc, + coin: &str, + protocol: CoinProtocol, +) -> Result<(Json, T), MmError> { + protocol + .custom_token_validations(ctx) + .mm_err(CoinConfWithProtocolError::CustomTokenError)?; + + let conf = json!({ + "protocol": protocol, + "wallet_only": true + }); + + let coin_protocol = + T::try_from_coin_protocol(protocol).mm_err(|p| CoinConfWithProtocolError::UnexpectedProtocol { ticker: coin.into(), - protocol, + protocol: json!(p), })?; - Ok((conf, protocol)) + + Ok((conf, coin_protocol)) } /// A trait to be implemented for coin activation requests to determine some information about the request. diff --git a/mm2src/coins_activation/src/sia_coin_activation.rs b/mm2src/coins_activation/src/sia_coin_activation.rs index 7dd7539f66..11c72955ab 100644 --- a/mm2src/coins_activation/src/sia_coin_activation.rs +++ b/mm2src/coins_activation/src/sia_coin_activation.rs @@ -7,8 +7,8 @@ use async_trait::async_trait; use coins::coin_balance::{CoinBalanceReport, IguanaWalletBalance}; use coins::coin_errors::MyAddressError; use coins::my_tx_history_v2::TxHistoryStorage; -use coins::sia::{sia_coin_from_conf_and_params, SiaCoin, SiaCoinActivationParams, SiaCoinBuildError, - SiaCoinProtocolInfo}; +use coins::siacoin::{sia_coin_from_conf_and_params, SiaCoin, SiaCoinActivationParams, SiaCoinBuildError, + SiaCoinProtocolInfo}; use coins::tx_history_storage::CreateTxHistoryStorageError; use coins::{BalanceError, CoinBalance, CoinProtocol, MarketCoinOps, PrivKeyBuildPolicy, RegisterCoinError}; use crypto::hw_rpc_task::{HwRpcTaskAwaitingStatus, HwRpcTaskUserAction}; diff --git a/mm2src/coins_activation/src/slp_token_activation.rs b/mm2src/coins_activation/src/slp_token_activation.rs index f9abc166f4..91dbf95ea7 100644 --- a/mm2src/coins_activation/src/slp_token_activation.rs +++ b/mm2src/coins_activation/src/slp_token_activation.rs @@ -7,6 +7,7 @@ use coins::{CoinBalance, CoinProtocol, MarketCoinOps, MmCoin, MmCoinEnum}; use mm2_err_handle::prelude::*; use rpc::v1::types::H256 as H256Json; use serde_derive::{Deserialize, Serialize}; +use serde_json::Value as Json; use std::collections::HashMap; impl TryPlatformCoinFromMmCoinEnum for BchCoin { @@ -82,7 +83,9 @@ impl TokenActivationOps for SlpToken { ticker: String, platform_coin: Self::PlatformCoin, activation_params: Self::ActivationParams, + _token_conf: Json, protocol_conf: Self::ProtocolInfo, + _is_custom: bool, ) -> Result<(Self, Self::ActivationResult), MmError> { // confirmation settings from activation params have the highest priority let required_confirmations = activation_params.required_confirmations.unwrap_or_else(|| { diff --git a/mm2src/coins_activation/src/solana_with_tokens_activation.rs b/mm2src/coins_activation/src/solana_with_tokens_activation.rs deleted file mode 100644 index f411995779..0000000000 --- a/mm2src/coins_activation/src/solana_with_tokens_activation.rs +++ /dev/null @@ -1,327 +0,0 @@ -use crate::context::CoinsActivationContext; -use crate::platform_coin_with_tokens::{EnablePlatformCoinWithTokensError, GetPlatformBalance, - InitPlatformCoinWithTokensAwaitingStatus, - InitPlatformCoinWithTokensInProgressStatus, InitPlatformCoinWithTokensTask, - InitPlatformCoinWithTokensTaskManagerShared, - InitPlatformCoinWithTokensUserAction, InitTokensAsMmCoinsError, - PlatformCoinWithTokensActivationOps, RegisterTokenInfo, TokenActivationParams, - TokenActivationRequest, TokenAsMmCoinInitializer, TokenInitializer, TokenOf}; -use crate::prelude::*; -use crate::prelude::{CoinAddressInfo, TokenBalances, TryFromCoinProtocol, TxHistory}; -use crate::spl_token_activation::SplActivationRequest; -use async_trait::async_trait; -use coins::coin_errors::MyAddressError; -use coins::my_tx_history_v2::TxHistoryStorage; -use coins::solana::solana_coin_with_policy; -use coins::solana::spl::{SplProtocolConf, SplTokenCreationError}; -use coins::{BalanceError, CoinBalance, CoinProtocol, DerivationMethodResponse, MarketCoinOps, MmCoinEnum, - PrivKeyBuildPolicy, SolanaActivationParams, SolanaCoin, SplToken}; -use common::Future01CompatExt; -use common::{drop_mutability, true_f}; -use crypto::CryptoCtxError; -use futures::future::try_join_all; -use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::prelude::*; -use mm2_event_stream::EventStreamConfiguration; -use mm2_number::BigDecimal; -use rpc_task::RpcTaskHandleShared; -use serde_derive::{Deserialize, Serialize}; -use serde_json::Value as Json; -use std::collections::HashMap; - -pub struct SplTokenInitializer { - platform_coin: SolanaCoin, -} - -impl TokenOf for SplToken { - type PlatformCoin = SolanaCoin; -} - -pub struct SplTokenInitializerErr { - ticker: String, - inner: SplTokenCreationError, -} - -#[async_trait] -impl TokenInitializer for SplTokenInitializer { - type Token = SplToken; - type TokenActivationRequest = SplActivationRequest; - type TokenProtocol = SplProtocolConf; - type InitTokensError = SplTokenInitializerErr; - - fn tokens_requests_from_platform_request( - platform_params: &SolanaWithTokensActivationRequest, - ) -> Vec> { - platform_params.spl_tokens_requests.clone() - } - - async fn enable_tokens( - &self, - activation_params: Vec>, - ) -> Result, MmError> { - let tokens = activation_params - .into_iter() - .map(|param| { - let ticker = param.ticker.clone(); - SplToken::new( - param.protocol.decimals, - param.ticker, - param.protocol.token_contract_address, - self.platform_coin.clone(), - ) - .mm_err(|inner| SplTokenInitializerErr { ticker, inner }) - }) - .collect::, _>>()?; - Ok(tokens) - } - - fn platform_coin(&self) -> &SolanaCoin { &self.platform_coin } -} - -impl RegisterTokenInfo for SolanaCoin { - fn register_token_info(&self, token: &SplToken) { self.add_spl_token_info(token.ticker().into(), token.get_info()) } -} - -#[derive(Clone, Debug, Deserialize)] -pub struct SolanaWithTokensActivationRequest { - #[serde(flatten)] - platform_request: SolanaActivationParams, - spl_tokens_requests: Vec>, - #[serde(default = "true_f")] - pub get_balances: bool, -} - -impl TxHistory for SolanaWithTokensActivationRequest { - fn tx_history(&self) -> bool { false } -} - -impl ActivationRequestInfo for SolanaWithTokensActivationRequest { - fn is_hw_policy(&self) -> bool { false } // TODO: fix when device policy is added -} - -#[derive(Debug, Serialize, Clone)] -pub struct SolanaWithTokensActivationResult { - current_block: u64, - solana_addresses_infos: HashMap>, - spl_addresses_infos: HashMap>, -} - -impl GetPlatformBalance for SolanaWithTokensActivationResult { - fn get_platform_balance(&self) -> Option { - self.solana_addresses_infos - .iter() - .fold(Some(BigDecimal::from(0)), |total, (_, addr_info)| { - total.and_then(|t| addr_info.balances.as_ref().map(|b| t + b.get_total())) - }) - } -} - -impl CurrentBlock for SolanaWithTokensActivationResult { - fn current_block(&self) -> u64 { self.current_block } -} - -#[derive(Debug, Clone)] -pub enum SolanaWithTokensActivationError { - PlatformCoinCreationError { ticker: String, error: String }, - UnableToRetrieveMyAddress(String), - GetBalanceError(BalanceError), - Transport(String), - Internal(String), -} - -impl From for SolanaWithTokensActivationError { - fn from(err: MyAddressError) -> Self { Self::UnableToRetrieveMyAddress(err.to_string()) } -} - -impl From for EnablePlatformCoinWithTokensError { - fn from(e: SolanaWithTokensActivationError) -> Self { - match e { - SolanaWithTokensActivationError::PlatformCoinCreationError { ticker, error } => { - EnablePlatformCoinWithTokensError::PlatformCoinCreationError { ticker, error } - }, - SolanaWithTokensActivationError::UnableToRetrieveMyAddress(e) => { - EnablePlatformCoinWithTokensError::Internal(e) - }, - SolanaWithTokensActivationError::GetBalanceError(e) => { - EnablePlatformCoinWithTokensError::Internal(format!("{:?}", e)) - }, - SolanaWithTokensActivationError::Transport(e) => EnablePlatformCoinWithTokensError::Transport(e), - SolanaWithTokensActivationError::Internal(e) => EnablePlatformCoinWithTokensError::Internal(e), - } - } -} - -impl From for SolanaWithTokensActivationError { - fn from(e: BalanceError) -> Self { SolanaWithTokensActivationError::GetBalanceError(e) } -} - -impl From for SolanaWithTokensActivationError { - fn from(e: CryptoCtxError) -> Self { SolanaWithTokensActivationError::Internal(e.to_string()) } -} - -pub struct SolanaProtocolInfo {} - -impl TryFromCoinProtocol for SolanaProtocolInfo { - fn try_from_coin_protocol(proto: CoinProtocol) -> Result> - where - Self: Sized, - { - match proto { - CoinProtocol::SOLANA {} => Ok(SolanaProtocolInfo {}), - protocol => MmError::err(protocol), - } - } -} - -impl From for InitTokensAsMmCoinsError { - fn from(err: SplTokenInitializerErr) -> Self { - match err.inner { - SplTokenCreationError::InvalidPubkey(error) => InitTokensAsMmCoinsError::TokenProtocolParseError { - ticker: err.ticker, - error, - }, - SplTokenCreationError::Internal(internal) => InitTokensAsMmCoinsError::Internal(internal), - } - } -} - -#[async_trait] -impl PlatformCoinWithTokensActivationOps for SolanaCoin { - type ActivationRequest = SolanaWithTokensActivationRequest; - type PlatformProtocolInfo = SolanaProtocolInfo; - type ActivationResult = SolanaWithTokensActivationResult; - type ActivationError = SolanaWithTokensActivationError; - - type InProgressStatus = InitPlatformCoinWithTokensInProgressStatus; - type AwaitingStatus = InitPlatformCoinWithTokensAwaitingStatus; - type UserAction = InitPlatformCoinWithTokensUserAction; - - async fn enable_platform_coin( - ctx: MmArc, - ticker: String, - platform_conf: &Json, - activation_request: Self::ActivationRequest, - _protocol_conf: Self::PlatformProtocolInfo, - ) -> Result> { - let priv_key_policy = PrivKeyBuildPolicy::detect_priv_key_policy(&ctx)?; - solana_coin_with_policy( - &ctx, - &ticker, - platform_conf, - activation_request.platform_request, - priv_key_policy, - ) - .await - .map_to_mm(|error| SolanaWithTokensActivationError::PlatformCoinCreationError { ticker, error }) - } - - async fn enable_global_nft( - &self, - _activation_request: &Self::ActivationRequest, - ) -> Result, MmError> { - Ok(None) - } - - fn try_from_mm_coin(coin: MmCoinEnum) -> Option - where - Self: Sized, - { - match coin { - MmCoinEnum::SolanaCoin(coin) => Some(coin), - _ => None, - } - } - - fn token_initializers( - &self, - ) -> Vec>> { - vec![Box::new(SplTokenInitializer { - platform_coin: self.clone(), - })] - } - - async fn get_activation_result( - &self, - _task_handle: Option>>, - activation_request: &Self::ActivationRequest, - _nft_global: &Option, - ) -> Result> { - let current_block = self - .current_block() - .compat() - .await - .map_to_mm(Self::ActivationError::Internal)?; - - let my_address = self.my_address()?; - - let mut solana_address_info = CoinAddressInfo { - derivation_method: DerivationMethodResponse::Iguana, - pubkey: my_address.clone(), - balances: None, - tickers: None, - }; - - let mut spl_address_info = CoinAddressInfo { - derivation_method: DerivationMethodResponse::Iguana, - pubkey: my_address.clone(), - balances: None, - tickers: None, - }; - - if !activation_request.get_balances { - drop_mutability!(solana_address_info); - let tickers = self.get_spl_tokens_infos().into_keys().collect(); - spl_address_info.tickers = Some(tickers); - drop_mutability!(spl_address_info); - - return Ok(SolanaWithTokensActivationResult { - current_block, - solana_addresses_infos: HashMap::from([(my_address.clone(), solana_address_info)]), - spl_addresses_infos: HashMap::from([(my_address, spl_address_info)]), - }); - } - - let solana_balance = self - .my_balance() - .compat() - .await - .map_err(|e| Self::ActivationError::GetBalanceError(e.into_inner()))?; - solana_address_info.balances = Some(solana_balance); - drop_mutability!(solana_address_info); - - let (token_tickers, requests): (Vec<_>, Vec<_>) = self - .get_spl_tokens_infos() - .into_iter() - .map(|(ticker, info)| (ticker, self.my_balance_spl(info))) - .unzip(); - spl_address_info.balances = Some(token_tickers.into_iter().zip(try_join_all(requests).await?).collect()); - drop_mutability!(spl_address_info); - - Ok(SolanaWithTokensActivationResult { - current_block, - solana_addresses_infos: HashMap::from([(my_address.clone(), solana_address_info)]), - spl_addresses_infos: HashMap::from([(my_address, spl_address_info)]), - }) - } - - fn start_history_background_fetching( - &self, - _ctx: MmArc, - _storage: impl TxHistoryStorage + Send + 'static, - _initial_balance: Option, - ) { - } - - async fn handle_balance_streaming( - &self, - _config: &EventStreamConfiguration, - ) -> Result<(), MmError> { - Ok(()) - } - - fn rpc_task_manager( - _activation_ctx: &CoinsActivationContext, - ) -> &InitPlatformCoinWithTokensTaskManagerShared { - unimplemented!() - } -} diff --git a/mm2src/coins_activation/src/spl_token_activation.rs b/mm2src/coins_activation/src/spl_token_activation.rs deleted file mode 100644 index 2bf40b6a89..0000000000 --- a/mm2src/coins_activation/src/spl_token_activation.rs +++ /dev/null @@ -1,117 +0,0 @@ -use crate::prelude::{TryFromCoinProtocol, TryPlatformCoinFromMmCoinEnum}; -use crate::token::{EnableTokenError, TokenActivationOps, TokenProtocolParams}; -use async_trait::async_trait; -use coins::coin_errors::MyAddressError; -use coins::solana::spl::{SplProtocolConf, SplTokenCreationError}; -use coins::{BalanceError, CoinBalance, CoinProtocol, MarketCoinOps, MmCoinEnum, SolanaCoin, SplToken}; -use common::Future01CompatExt; -use mm2_err_handle::prelude::*; -use serde_derive::{Deserialize, Serialize}; -use std::collections::HashMap; - -impl TryPlatformCoinFromMmCoinEnum for SolanaCoin { - fn try_from_mm_coin(coin: MmCoinEnum) -> Option - where - Self: Sized, - { - match coin { - MmCoinEnum::SolanaCoin(coin) => Some(coin), - _ => None, - } - } -} - -#[derive(Clone, Debug, Deserialize)] -pub struct SplActivationRequest {} - -impl TryFromCoinProtocol for SplProtocolConf { - fn try_from_coin_protocol(proto: CoinProtocol) -> Result> - where - Self: Sized, - { - match proto { - CoinProtocol::SPLTOKEN { - platform, - token_contract_address, - decimals, - } => Ok(SplProtocolConf { - platform_coin_ticker: platform, - decimals, - token_contract_address, - }), - proto => MmError::err(proto), - } - } -} - -impl TokenProtocolParams for SplProtocolConf { - fn platform_coin_ticker(&self) -> &str { &self.platform_coin_ticker } -} - -#[derive(Debug, Serialize)] -pub struct SplInitResult { - balances: HashMap, - token_contract_address: String, - platform_coin: String, -} - -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -pub enum SplInitError { - GetBalanceError(BalanceError), - TokenCreationFailed(SplTokenCreationError), - MyAddressError(String), -} - -impl From for SplInitError { - fn from(err: MyAddressError) -> Self { Self::MyAddressError(err.to_string()) } -} - -impl From for SplInitError { - fn from(e: SplTokenCreationError) -> Self { SplInitError::TokenCreationFailed(e) } -} - -impl From for EnableTokenError { - fn from(err: SplInitError) -> Self { - match err { - SplInitError::GetBalanceError(rpc_err) => rpc_err.into(), - SplInitError::TokenCreationFailed(e) => EnableTokenError::Internal(format! {"{:?}", e}), - SplInitError::MyAddressError(e) => EnableTokenError::Internal(e), - } - } -} - -#[async_trait] -impl TokenActivationOps for SplToken { - type ActivationParams = SplActivationRequest; - type ProtocolInfo = SplProtocolConf; - type ActivationResult = SplInitResult; - type ActivationError = SplInitError; - - async fn enable_token( - ticker: String, - platform_coin: Self::PlatformCoin, - _activation_params: Self::ActivationParams, - protocol_conf: Self::ProtocolInfo, - ) -> Result<(Self, Self::ActivationResult), MmError> { - let token = Self::new( - protocol_conf.decimals, - ticker, - protocol_conf.token_contract_address, - platform_coin, - )?; - let balance = token - .my_balance() - .compat() - .await - .map_err(|e| SplInitError::GetBalanceError(e.into_inner()))?; - let my_address = token.my_address()?; - let balances = HashMap::from([(my_address, balance)]); - let init_result = SplInitResult { - balances, - token_contract_address: token.conf.token_contract_address.to_string(), - platform_coin: token.platform_coin.ticker().to_owned(), - }; - Ok((token, init_result)) - } -} diff --git a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs index 314f3066b4..a90f53e968 100644 --- a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs +++ b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs @@ -90,7 +90,7 @@ where return MmError::err(InitStandaloneCoinError::CoinIsAlreadyActivated { ticker: request.ticker }); } - let (coin_conf, protocol_info) = coin_conf_with_protocol(&ctx, &request.ticker)?; + let (coin_conf, protocol_info) = coin_conf_with_protocol(&ctx, &request.ticker, None)?; let coins_act_ctx = CoinsActivationContext::from_ctx(&ctx).map_to_mm(InitStandaloneCoinError::Internal)?; let spawner = ctx.spawner(); diff --git a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs index 1f0b5db764..21b1d696a9 100644 --- a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs +++ b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs @@ -1,5 +1,4 @@ use crate::prelude::CoinConfWithProtocolError; -use coins::CoinProtocol; use common::{HttpStatusCode, StatusCode}; use crypto::HwRpcError; use derive_more::Display; @@ -7,6 +6,7 @@ use rpc_task::rpc_common::{CancelRpcTaskError, RpcTaskStatusError, RpcTaskUserAc use rpc_task::{RpcTaskError, TaskId}; use ser_error_derive::SerializeErrorType; use serde_derive::Serialize; +use serde_json::Value as Json; use std::time::Duration; pub type InitStandaloneCoinStatusError = RpcTaskStatusError; @@ -26,8 +26,8 @@ pub enum InitStandaloneCoinError { CoinConfigIsNotFound(String), #[display(fmt = "Coin {} protocol parsing failed: {}", ticker, error)] CoinProtocolParseError { ticker: String, error: String }, - #[display(fmt = "Unexpected platform protocol {:?} for {}", protocol, ticker)] - UnexpectedCoinProtocol { ticker: String, protocol: CoinProtocol }, + #[display(fmt = "Unexpected platform protocol {} for {}", protocol, ticker)] + UnexpectedCoinProtocol { ticker: String, protocol: Json }, #[display(fmt = "Error on platform coin {} creation: {}", ticker, error)] CoinCreationError { ticker: String, error: String }, #[display(fmt = "{}", _0)] @@ -51,6 +51,10 @@ impl From for InitStandaloneCoinError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitStandaloneCoinError::UnexpectedCoinProtocol { ticker, protocol } }, + CoinConfWithProtocolError::CustomTokenError(e) => InitStandaloneCoinError::Internal(format!( + "Custom tokens are not supported for standalone coins: {}", + e + )), } } } diff --git a/mm2src/coins_activation/src/tendermint_token_activation.rs b/mm2src/coins_activation/src/tendermint_token_activation.rs index 29598969af..12808505a9 100644 --- a/mm2src/coins_activation/src/tendermint_token_activation.rs +++ b/mm2src/coins_activation/src/tendermint_token_activation.rs @@ -7,6 +7,7 @@ use coins::{tendermint::{TendermintCoin, TendermintToken, TendermintTokenActivat use common::Future01CompatExt; use mm2_err_handle::prelude::{MapMmError, MmError}; use serde::Serialize; +use serde_json::Value as Json; use std::collections::HashMap; impl From for EnableTokenError { @@ -54,7 +55,9 @@ impl TokenActivationOps for TendermintToken { ticker: String, platform_coin: Self::PlatformCoin, _activation_params: Self::ActivationParams, + _token_conf: Json, protocol_conf: Self::ProtocolInfo, + _is_custom: bool, ) -> Result<(Self, Self::ActivationResult), MmError> { let token = TendermintToken::new(ticker, platform_coin, protocol_conf.decimals, protocol_conf.denom)?; diff --git a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs index d413175364..349e37b23d 100644 --- a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs +++ b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs @@ -11,9 +11,9 @@ use async_trait::async_trait; use coins::hd_wallet::HDPathAccountToAddressId; use coins::my_tx_history_v2::TxHistoryStorage; use coins::tendermint::tendermint_tx_history_v2::tendermint_history_loop; -use coins::tendermint::{tendermint_priv_key_policy, TendermintActivationPolicy, TendermintCoin, TendermintCommons, - TendermintConf, TendermintInitError, TendermintInitErrorKind, TendermintProtocolInfo, - TendermintPublicKey, TendermintToken, TendermintTokenActivationParams, +use coins::tendermint::{tendermint_priv_key_policy, RpcNode, TendermintActivationPolicy, TendermintCoin, + TendermintCommons, TendermintConf, TendermintInitError, TendermintInitErrorKind, + TendermintProtocolInfo, TendermintPublicKey, TendermintToken, TendermintTokenActivationParams, TendermintTokenInitError, TendermintTokenProtocolInfo}; use coins::{CoinBalance, CoinProtocol, MarketCoinOps, MmCoin, MmCoinEnum, PrivKeyBuildPolicy}; use common::executor::{AbortSettings, SpawnAbortable}; @@ -40,7 +40,7 @@ impl RegisterTokenInfo for TendermintCoin { #[derive(Clone, Deserialize)] pub struct TendermintActivationParams { - rpc_urls: Vec, + nodes: Vec, pub tokens_params: Vec>, #[serde(default)] tx_history: bool, @@ -52,6 +52,8 @@ pub struct TendermintActivationParams { #[serde(default)] #[serde(deserialize_with = "deserialize_account_public_key")] with_pubkey: Option, + #[serde(default)] + is_keplr_from_ledger: bool, } fn deserialize_account_public_key<'de, D>(deserializer: D) -> Result, D::Error> @@ -234,6 +236,7 @@ impl PlatformCoinWithTokensActivationOps for TendermintCoin { protocol_conf: Self::PlatformProtocolInfo, ) -> Result> { let conf = TendermintConf::try_from_json(&ticker, coin_conf)?; + let is_keplr_from_ledger = activation_request.is_keplr_from_ledger && activation_request.with_pubkey.is_some(); let activation_policy = if let Some(pubkey) = activation_request.with_pubkey { if ctx.is_watcher() || ctx.use_watchers() { @@ -262,9 +265,10 @@ impl PlatformCoinWithTokensActivationOps for TendermintCoin { ticker, conf, protocol_conf, - activation_request.rpc_urls, + activation_request.nodes, activation_request.tx_history, activation_policy, + is_keplr_from_ledger, ) .await } diff --git a/mm2src/coins_activation/src/token.rs b/mm2src/coins_activation/src/token.rs index d1449dea8d..5001a0f3de 100644 --- a/mm2src/coins_activation/src/token.rs +++ b/mm2src/coins_activation/src/token.rs @@ -4,14 +4,15 @@ use crate::platform_coin_with_tokens::{self, RegisterTokenInfo}; use crate::prelude::*; use async_trait::async_trait; use coins::utxo::rpc_clients::UtxoRpcError; -use coins::{lp_coinfind, lp_coinfind_or_err, BalanceError, CoinProtocol, CoinsContext, MmCoinEnum, RegisterCoinError, - UnexpectedDerivationMethod}; +use coins::{lp_coinfind, lp_coinfind_or_err, BalanceError, CoinProtocol, CoinsContext, CustomTokenError, MmCoinEnum, + PrivKeyPolicyNotAllowed, RegisterCoinError, UnexpectedDerivationMethod}; use common::{HttpStatusCode, StatusCode}; use derive_more::Display; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use ser_error_derive::SerializeErrorType; use serde_derive::{Deserialize, Serialize}; +use serde_json::Value as Json; pub trait TokenProtocolParams { fn platform_coin_ticker(&self) -> &str; @@ -28,7 +29,9 @@ pub trait TokenActivationOps: Into + platform_coin_with_tokens::Toke ticker: String, platform_coin: Self::PlatformCoin, activation_params: Self::ActivationParams, + token_conf: Json, protocol_conf: Self::ProtocolInfo, + is_custom: bool, ) -> Result<(Self, Self::ActivationResult), MmError>; } @@ -44,10 +47,10 @@ pub enum EnableTokenError { ticker: String, error: String, }, - #[display(fmt = "Unexpected token protocol {:?} for {}", protocol, ticker)] + #[display(fmt = "Unexpected token protocol {} for {}", protocol, ticker)] UnexpectedTokenProtocol { ticker: String, - protocol: CoinProtocol, + protocol: Json, }, #[display(fmt = "Platform coin {} is not activated", _0)] PlatformCoinIsNotActivated(String), @@ -63,6 +66,9 @@ pub enum EnableTokenError { Transport(String), Internal(String), InvalidPayload(String), + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(CustomTokenError), } impl From for EnableTokenError { @@ -87,6 +93,7 @@ impl From for EnableTokenError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { EnableTokenError::UnexpectedTokenProtocol { ticker, protocol } }, + CoinConfWithProtocolError::CustomTokenError(e) => EnableTokenError::CustomTokenError(e), } } } @@ -104,6 +111,7 @@ impl From for EnableTokenError { #[derive(Debug, Deserialize)] pub struct EnableTokenRequest { ticker: String, + protocol: Option, activation_params: T, } @@ -120,7 +128,8 @@ where return MmError::err(EnableTokenError::TokenIsAlreadyActivated(req.ticker)); } - let (_, token_protocol): (_, Token::ProtocolInfo) = coin_conf_with_protocol(&ctx, &req.ticker)?; + let (token_conf, token_protocol): (_, Token::ProtocolInfo) = + coin_conf_with_protocol(&ctx, &req.ticker, req.protocol.clone())?; let platform_coin = lp_coinfind_or_err(&ctx, token_protocol.platform_coin_ticker()) .await @@ -133,8 +142,15 @@ where } })?; - let (token, activation_result) = - Token::enable_token(req.ticker, platform_coin.clone(), req.activation_params, token_protocol).await?; + let (token, activation_result) = Token::enable_token( + req.ticker, + platform_coin.clone(), + req.activation_params, + token_conf, + token_protocol, + req.protocol.is_some(), + ) + .await?; let coins_ctx = CoinsContext::from_ctx(&ctx).unwrap(); coins_ctx.add_token(token.clone().into()).await?; @@ -163,14 +179,16 @@ impl HttpStatusCode for EnableTokenError { | EnableTokenError::PlatformCoinIsNotActivated(_) | EnableTokenError::TokenConfigIsNotFound { .. } | EnableTokenError::UnexpectedTokenProtocol { .. } - | EnableTokenError::InvalidPayload(_) => StatusCode::BAD_REQUEST, + | EnableTokenError::InvalidPayload(_) + | EnableTokenError::CustomTokenError(_) => StatusCode::BAD_REQUEST, EnableTokenError::TokenProtocolParseError { .. } | EnableTokenError::UnsupportedPlatformCoin { .. } | EnableTokenError::UnexpectedDerivationMethod(_) | EnableTokenError::Transport(_) | EnableTokenError::CouldNotFetchBalance(_) | EnableTokenError::InvalidConfig(_) - | EnableTokenError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, + | EnableTokenError::Internal(_) + | EnableTokenError::PrivKeyPolicyNotAllowed(_) => StatusCode::INTERNAL_SERVER_ERROR, } } } diff --git a/mm2src/coins_activation/src/utxo_activation/common_impl.rs b/mm2src/coins_activation/src/utxo_activation/common_impl.rs index 8e3d071025..43cc0e32d6 100644 --- a/mm2src/coins_activation/src/utxo_activation/common_impl.rs +++ b/mm2src/coins_activation/src/utxo_activation/common_impl.rs @@ -8,7 +8,7 @@ use coins::hd_wallet::RpcTaskXPubExtractor; use coins::my_tx_history_v2::TxHistoryStorage; use coins::utxo::utxo_tx_history_v2::{utxo_history_loop, UtxoTxHistoryOps}; use coins::utxo::{UtxoActivationParams, UtxoCoinFields}; -use coins::{CoinBalance, CoinFutSpawner, MarketCoinOps, PrivKeyActivationPolicy, PrivKeyBuildPolicy}; +use coins::{CoinBalanceMap, CoinFutSpawner, MarketCoinOps, PrivKeyActivationPolicy, PrivKeyBuildPolicy}; use common::executor::{AbortSettings, SpawnAbortable}; use crypto::hw_rpc_task::HwConnectStatuses; use crypto::{CryptoCtxError, HwRpcError}; @@ -31,7 +31,7 @@ where InProgressStatus = UtxoStandardInProgressStatus, AwaitingStatus = UtxoStandardAwaitingStatus, UserAction = UtxoStandardUserAction, - > + EnableCoinBalanceOps + > + EnableCoinBalanceOps + MarketCoinOps, { let ticker = coin.ticker().to_owned(); diff --git a/mm2src/coins_activation/src/utxo_activation/utxo_standard_activation_result.rs b/mm2src/coins_activation/src/utxo_activation/utxo_standard_activation_result.rs index d9f67ae9b5..2fd75e5ab8 100644 --- a/mm2src/coins_activation/src/utxo_activation/utxo_standard_activation_result.rs +++ b/mm2src/coins_activation/src/utxo_activation/utxo_standard_activation_result.rs @@ -1,6 +1,6 @@ use crate::prelude::{CurrentBlock, GetAddressesBalances}; use coins::coin_balance::CoinBalanceReport; -use coins::CoinBalance; +use coins::CoinBalanceMap; use mm2_number::BigDecimal; use serde_derive::Serialize; use std::collections::HashMap; @@ -9,7 +9,7 @@ use std::collections::HashMap; pub struct UtxoStandardActivationResult { pub ticker: String, pub current_block: u64, - pub wallet_balance: CoinBalanceReport, + pub wallet_balance: CoinBalanceReport, } impl CurrentBlock for UtxoStandardActivationResult { diff --git a/mm2src/common/Cargo.toml b/mm2src/common/Cargo.toml index 6549d02cc1..b775741cf8 100644 --- a/mm2src/common/Cargo.toml +++ b/mm2src/common/Cargo.toml @@ -25,6 +25,7 @@ fnv = "1.0.6" futures01 = { version = "0.1", package = "futures" } futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } futures-timer = "3.0" +gstuff = "0.7" hex = "0.4.2" http = "0.2" http-body = "0.1" @@ -33,6 +34,7 @@ lazy_static = "1.4" log = "0.4.17" parking_lot = { version = "0.12.0", features = ["nightly"] } parking_lot_core = { version = "0.6", features = ["nightly"] } +paste = "1.0" primitive-types = "0.11.1" rand = { version = "0.7", features = ["std", "small_rng"] } rustc-hash = "1.1.0" @@ -49,7 +51,6 @@ instant = { version = "0.1.12" } [target.'cfg(target_arch = "wasm32")'.dependencies] chrono = { version = "0.4", features = ["wasmbind"] } -gstuff = { version = "0.7", features = ["nightly"] } instant = { version = "0.1.12", features = ["wasm-bindgen"] } js-sys = "0.3.27" serde_repr = "0.1.6" @@ -62,7 +63,6 @@ web-sys = { version = "0.3.55", features = ["console", "CloseEvent", "DomExcepti [target.'cfg(not(target_arch = "wasm32"))'.dependencies] anyhow = "1.0" chrono = "0.4" -gstuff = { version = "0.7", features = ["nightly"] } hyper = { version = "0.14.26", features = ["client", "http2", "server", "tcp"] } # using webpki-tokio to avoid rejecting valid certificates # got "invalid certificate: UnknownIssuer" for https://ropsten.infura.io on iOS using default-features @@ -79,4 +79,4 @@ findshlibs = "0.5" [build-dependencies] cc = "1.0" -gstuff = { version = "0.7", features = ["nightly"] } +gstuff = "0.7" diff --git a/mm2src/common/common.rs b/mm2src/common/common.rs index 552abae1d4..288882d0ae 100644 --- a/mm2src/common/common.rs +++ b/mm2src/common/common.rs @@ -129,6 +129,7 @@ pub mod custom_futures; pub mod custom_iter; #[path = "executor/mod.rs"] pub mod executor; pub mod expirable_map; +pub mod notifier; pub mod number_type_casting; pub mod password_policy; pub mod seri; @@ -151,6 +152,7 @@ use futures01::{future, Future}; use http::header::CONTENT_TYPE; use http::Response; use parking_lot::{Mutex as PaMutex, MutexGuard as PaMutexGuard}; +pub use paste::paste; use rand::RngCore; use rand::{rngs::SmallRng, SeedableRng}; use serde::{de, ser}; @@ -160,14 +162,14 @@ use std::convert::TryInto; use std::fmt::Write as FmtWrite; use std::fs::File; use std::future::Future as Future03; -use std::io::{BufReader, Read, Write}; +use std::io::{self, BufReader, Read, Write}; use std::iter::Peekable; use std::mem::{forget, zeroed}; use std::num::{NonZeroUsize, TryFromIntError}; use std::ops::{Add, Deref, Div, RangeInclusive}; use std::os::raw::c_void; use std::panic::{set_hook, PanicInfo}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::ptr::read_volatile; use std::sync::atomic::Ordering; use std::time::{Duration, SystemTime, SystemTimeError}; @@ -193,7 +195,7 @@ cfg_wasm32! { const KOMODO_DEFI_FRAMEWORK_DIR_NAME: &str = ".kdf"; pub const X_GRPC_WEB: &str = "x-grpc-web"; -pub const X_API_KEY: &str = "X-API-Key"; +pub const X_AUTH_PAYLOAD: &str = "X-Auth-Payload"; pub const APPLICATION_JSON: &str = "application/json"; pub const APPLICATION_GRPC_WEB: &str = "application/grpc-web"; pub const APPLICATION_GRPC_WEB_PROTO: &str = "application/grpc-web+proto"; @@ -204,6 +206,8 @@ pub const SATOSHIS: u64 = 100_000_000; pub const DEX_FEE_ADDR_PUBKEY: &str = "03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc06"; +pub const PROXY_REQUEST_EXPIRATION_SEC: i64 = 15; + lazy_static! { pub static ref DEX_FEE_ADDR_RAW_PUBKEY: Vec = hex::decode(DEX_FEE_ADDR_PUBKEY).expect("DEX_FEE_ADDR_PUBKEY is expected to be a hexadecimal string"); @@ -356,7 +360,7 @@ pub fn filename(path: &str) -> &str { /// Some common and less than useful frames are skipped. pub fn stack_trace_frame(instr_ptr: *mut c_void, buf: &mut dyn Write, symbol: &backtrace::Symbol) { let filename = match symbol.filename() { - Some(path) => match path.components().rev().next() { + Some(path) => match path.components().next_back() { Some(c) => c.as_os_str().to_string_lossy(), None => "??".into(), }, @@ -375,10 +379,9 @@ pub fn stack_trace_frame(instr_ptr: *mut c_void, buf: &mut dyn Write, symbol: &b // Skip common and less than informative frames. match name { - "mm2::crash_reports::rust_seh_handler" + "common::crash_reports::rust_seh_handler" | "veh_exception_filter" | "common::stack_trace" - | "common::log_stacktrace" // Super-main on Windows. | "__scrt_common_main_seh" => return, _ => (), @@ -396,7 +399,7 @@ pub fn stack_trace_frame(instr_ptr: *mut c_void, buf: &mut dyn Write, symbol: &b || name.starts_with("core::ops::") || name.starts_with("futures::") || name.starts_with("hyper::") - || name.starts_with("mm2::crash_reports::signal_handler") + || name.starts_with("common::crash_reports::signal_handler") || name.starts_with("panic_unwind::") || name.starts_with("std::") || name.starts_with("scoped_tls::") @@ -517,19 +520,6 @@ pub fn set_panic_hook() { })) } -/// Simulates the panic-in-panic crash. -pub fn double_panic_crash() { - struct Panicker; - impl Drop for Panicker { - fn drop(&mut self) { panic!("panic in drop") } - } - let panicker = Panicker; - if 1 < 2 { - panic!("first panic") - } - drop(panicker) // Delays the drop. -} - /// RPC response, returned by the RPC handlers. /// NB: By default the future is executed on the shared asynchronous reactor (`CORE`), /// the handler is responsible for spawning the future on another reactor if it doesn't fit the `CORE` well. @@ -633,7 +623,20 @@ pub fn var(name: &str) -> Result { #[cfg(target_arch = "wasm32")] pub fn var(_name: &str) -> Result { ERR!("Environment variable not supported in WASM") } +/// Runs the given future on MM2's executor and waits for the result. +/// +/// This is compatible with futures 0.1. +pub fn block_on_f01(f: F) -> Result +where + F: Future, +{ + block_on(f.compat()) +} + #[cfg(not(target_arch = "wasm32"))] +/// Runs the given future on MM2's executor and waits for the result. +/// +/// This is compatible with futures 0.3. pub fn block_on(f: F) -> F::Output where F: Future03, @@ -789,7 +792,7 @@ pub fn kdf_app_dir() -> Option { } /// Returns path of the coins file. -pub fn kdf_coins_file() -> PathBuf { +pub fn kdf_coins_file() -> Result { #[cfg(not(target_arch = "wasm32"))] let value_from_env = env::var("MM_COINS_PATH").ok(); @@ -800,7 +803,7 @@ pub fn kdf_coins_file() -> PathBuf { } /// Returns path of the config file. -pub fn kdf_config_file() -> PathBuf { +pub fn kdf_config_file() -> Result { #[cfg(not(target_arch = "wasm32"))] let value_from_env = env::var("MM_CONF_PATH").ok(); @@ -816,16 +819,41 @@ pub fn kdf_config_file() -> PathBuf { /// 1- From the environment variable. /// 2- From the current directory where app is called. /// 3- From the root application directory. -pub fn find_kdf_dependency_file(value_from_env: Option, path_leaf: &str) -> PathBuf { +fn find_kdf_dependency_file(value_from_env: Option, path_leaf: &str) -> Result { if let Some(path) = value_from_env { - return PathBuf::from(path); + let path = PathBuf::from(path); + require_file(&path)?; + return Ok(path); } let from_current_dir = PathBuf::from(path_leaf); - if from_current_dir.exists() { + + let path = if from_current_dir.exists() { from_current_dir } else { kdf_app_dir().unwrap_or_default().join(path_leaf) + }; + + require_file(&path)?; + return Ok(path); + + fn require_file(path: &Path) -> Result<(), io::Error> { + if path.is_dir() { + // TODO: use `IsADirectory` variant which is stabilized with 1.83 + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("Expected file but '{}' is a directory.", path.display()), + )); + } + + if !path.exists() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + format!("File '{}' is not present.", path.display()), + )); + } + + Ok(()) } } @@ -1121,9 +1149,41 @@ pub fn http_uri_to_ws_address(uri: http::Uri) -> String { }; let host_address = uri.host().expect("Host can't be empty."); + let path = if uri.path() == "/" { "" } else { uri.path() }; let port = uri.port_u16().map(|p| format!(":{}", p)).unwrap_or_default(); - format!("{}{}{}", address_prefix, host_address, port) + format!("{}{}{}{}", address_prefix, host_address, port, path) +} + +/// If 0x prefix exists in an str strip it or return the str as-is +#[macro_export] +macro_rules! str_strip_0x { + ($s: expr) => { + $s.strip_prefix("0x").unwrap_or($s) + }; +} + +/// If value is 'some' push key and value (as string) into an array containing (key, value) elements +#[macro_export] +macro_rules! push_if_some { + ($arr: expr, $k: expr, $v: expr) => { + if let Some(v) = $v { + $arr.push(($k, v.to_string())) + } + }; +} + +/// Define 'with_...' method to set a parameter with an optional value in a builder +#[macro_export] +macro_rules! def_with_opt_param { + ($var: ident, $var_type: ty) => { + $crate::paste! { + pub fn [](&mut self, $var: Option<$var_type>) -> &mut Self { + self.$var = $var; + self + } + } + }; } #[test] @@ -1132,13 +1192,22 @@ fn test_http_uri_to_ws_address() { let ws_connection = http_uri_to_ws_address(uri); assert_eq!(ws_connection, "wss://cosmos-rpc.polkachu.com"); - let uri = "http://cosmos-rpc.polkachu.com".parse::().unwrap(); + let uri = "http://cosmos-rpc.polkachu.com/".parse::().unwrap(); let ws_connection = http_uri_to_ws_address(uri); assert_eq!(ws_connection, "ws://cosmos-rpc.polkachu.com"); let uri = "http://34.82.96.8:26657".parse::().unwrap(); let ws_connection = http_uri_to_ws_address(uri); assert_eq!(ws_connection, "ws://34.82.96.8:26657"); + + let uri = "https://cosmos.blockpi.network/rpc/v1/65cc8a9ffe1627352b911dd4b7c751db4a3eaee3" + .parse::() + .unwrap(); + let ws_connection = http_uri_to_ws_address(uri); + assert_eq!( + ws_connection, + "wss://cosmos.blockpi.network/rpc/v1/65cc8a9ffe1627352b911dd4b7c751db4a3eaee3" + ); } #[test] diff --git a/mm2src/common/crash_reports.rs b/mm2src/common/crash_reports.rs index a749824015..7d9c7ad8cc 100644 --- a/mm2src/common/crash_reports.rs +++ b/mm2src/common/crash_reports.rs @@ -107,15 +107,19 @@ pub fn init_crash_reports() { set_panic_hook(); // Try to invoke the `rust_seh_handler` whenever the C code crashes. - if cfg!(windows) { + #[cfg(windows)] + { extern "C" { fn init_veh(); } unsafe { init_veh(); } - } else if cfg!(unix) { - init_signal_handling() + } + + #[cfg(unix)] + { + init_signal_handling(); } // Log Rust panics. diff --git a/mm2src/common/custom_futures/repeatable.rs b/mm2src/common/custom_futures/repeatable.rs index fbc1a8c9e8..3aaba119c7 100644 --- a/mm2src/common/custom_futures/repeatable.rs +++ b/mm2src/common/custom_futures/repeatable.rs @@ -208,7 +208,7 @@ where until: RepeatUntil::default(), repeat_every: DEFAULT_REPEAT_EVERY, inspect_err: None, - _phantom: PhantomData::default(), + _phantom: Default::default(), } } diff --git a/mm2src/common/executor/abortable_system/abortable_queue.rs b/mm2src/common/executor/abortable_system/abortable_queue.rs index 99ffc70ca3..89781bcfa4 100644 --- a/mm2src/common/executor/abortable_system/abortable_queue.rs +++ b/mm2src/common/executor/abortable_system/abortable_queue.rs @@ -40,9 +40,7 @@ impl From> for AbortableQueue { impl AbortableSystem for AbortableQueue { type Inner = QueueInnerState; - /// Aborts all spawned futures and initiates aborting of critical futures - /// after the specified [`AbortSettings::critical_timeout_s`]. - fn abort_all(&self) -> Result<(), AbortedError> { self.inner.lock().abort_all() } + fn __inner(&self) -> InnerShared { self.inner.clone() } fn __push_subsystem_abort_tx(&self, subsystem_abort_tx: oneshot::Sender<()>) -> Result<(), AbortedError> { self.inner.lock().insert_handle(subsystem_abort_tx).map(|_| ()) @@ -98,12 +96,15 @@ impl WeakSpawner { match select(abortable_fut.boxed(), wait_till_abort.boxed()).await { // The future has finished normally. - Either::Left(_) => { + Either::Left((_, wait_till_abort_fut)) => { if let Some(on_finish) = settings.on_finish { log::log!(on_finish.level, "{}", on_finish.msg); } if let Some(queue_inner) = inner_weak.upgrade() { + // Drop the `wait_till_abort_fut` so to render the corresponding `abort_tx` sender canceled. + // This way we can query the `abort_tx` sender to check if it's canceled, thus safe to mark as finished. + drop(wait_till_abort_fut); queue_inner.lock().on_future_finished(future_id); } }, @@ -203,8 +204,18 @@ impl QueueInnerState { /// Releases the `finished_future_id` so it can be reused later on [`QueueInnerState::insert_handle`]. fn on_future_finished(&mut self, finished_future_id: FutureId) { - if let QueueInnerState::Ready { finished_futures, .. } = self { - finished_futures.push(finished_future_id); + if let QueueInnerState::Ready { + finished_futures, + abort_handlers, + } = self + { + // Only mark this ID as finished if a future existed for it and is canceled. We can get false + // `on_future_finished` signals from futures that aren't in the `abort_handlers` anymore (abortable queue was reset). + if let Some(handle) = abort_handlers.get(finished_future_id) { + if handle.is_canceled() { + finished_futures.push(finished_future_id); + } + } } } @@ -234,6 +245,8 @@ impl SystemInner for QueueInnerState { *self = QueueInnerState::Aborted; Ok(()) } + + fn is_aborted(&self) -> bool { matches!(self, QueueInnerState::Aborted) } } #[cfg(test)] diff --git a/mm2src/common/executor/abortable_system/graceful_shutdown.rs b/mm2src/common/executor/abortable_system/graceful_shutdown.rs index 3feee076b2..6a902faab7 100644 --- a/mm2src/common/executor/abortable_system/graceful_shutdown.rs +++ b/mm2src/common/executor/abortable_system/graceful_shutdown.rs @@ -32,7 +32,7 @@ impl From> for GracefulShutdownRegistry { impl AbortableSystem for GracefulShutdownRegistry { type Inner = ShutdownInnerState; - fn abort_all(&self) -> Result<(), AbortedError> { self.inner.lock().abort_all() } + fn __inner(&self) -> InnerShared { self.inner.clone() } fn __push_subsystem_abort_tx(&self, subsystem_abort_tx: oneshot::Sender<()>) -> Result<(), AbortedError> { self.inner.lock().insert_handle(subsystem_abort_tx) @@ -73,4 +73,6 @@ impl SystemInner for ShutdownInnerState { *self = ShutdownInnerState::Aborted; Ok(()) } + + fn is_aborted(&self) -> bool { matches!(self, ShutdownInnerState::Aborted) } } diff --git a/mm2src/common/executor/abortable_system/mod.rs b/mm2src/common/executor/abortable_system/mod.rs index b5399ad6dd..82ef564278 100644 --- a/mm2src/common/executor/abortable_system/mod.rs +++ b/mm2src/common/executor/abortable_system/mod.rs @@ -24,7 +24,23 @@ pub trait AbortableSystem: From> { /// Aborts all spawned futures and subsystems if they present. /// The abortable system is considered not to be - fn abort_all(&self) -> Result<(), AbortedError>; + fn abort_all(&self) -> Result<(), AbortedError> { self.__inner().lock().abort_all() } + + /// Aborts all the spawned futures & subsystems if present, and resets the system + /// to the initial state for further use. + fn abort_all_and_reset(&self) -> Result<(), AbortedError> { + let inner = self.__inner(); + let mut inner_locked = inner.lock(); + // Don't allow resetting the system state if the system is already aborted. If the system is + // aborted this is because its parent was aborted as well. Resetting it will leave the system + // dangling with no parent to abort it (could still be aborted manually of course). + if inner_locked.is_aborted() { + return Err(AbortedError); + } + let mut previous_inner = std::mem::take(&mut *inner_locked); + previous_inner.abort_all().ok(); + Ok(()) + } /// Creates a new subsystem `S` linked to `Self` the way that /// if `Self` is aborted, the futures spawned by the subsystem will be aborted as well. @@ -56,12 +72,17 @@ pub trait AbortableSystem: From> { Ok(S::from(inner_shared)) } + fn __inner(&self) -> InnerShared; + fn __push_subsystem_abort_tx(&self, subsystem_abort_tx: oneshot::Sender<()>) -> Result<(), AbortedError>; } pub trait SystemInner: Default + Send + 'static { /// Aborts all spawned futures and subsystems if they present. fn abort_all(&mut self) -> Result<(), AbortedError>; + + /// Returns whether the system has already been aborted. + fn is_aborted(&self) -> bool; } #[cfg(test)] diff --git a/mm2src/common/executor/abortable_system/simple_map.rs b/mm2src/common/executor/abortable_system/simple_map.rs index c7cd9fc6cd..d759d53a04 100644 --- a/mm2src/common/executor/abortable_system/simple_map.rs +++ b/mm2src/common/executor/abortable_system/simple_map.rs @@ -35,7 +35,7 @@ impl AbortableSimpleMap { impl AbortableSystem for AbortableSimpleMap { type Inner = SimpleMapInnerState; - fn abort_all(&self) -> Result<(), AbortedError> { self.inner.lock().abort_all() } + fn __inner(&self) -> InnerShared { self.inner.clone() } fn __push_subsystem_abort_tx(&self, subsystem_abort_tx: oneshot::Sender<()>) -> Result<(), AbortedError> { self.inner.lock().insert_subsystem(subsystem_abort_tx) @@ -81,6 +81,8 @@ impl SystemInner for SimpleMapInnerState { *self = SimpleMapInnerState::Aborted; Ok(()) } + + fn is_aborted(&self) -> bool { matches!(self, SimpleMapInnerState::Aborted) } } impl SimpleMapInnerState { diff --git a/mm2src/common/expirable_map.rs b/mm2src/common/expirable_map.rs index 9b85dea84c..0b3110c066 100644 --- a/mm2src/common/expirable_map.rs +++ b/mm2src/common/expirable_map.rs @@ -5,7 +5,7 @@ use instant::{Duration, Instant}; use rustc_hash::FxHashMap; -use std::hash::Hash; +use std::{collections::BTreeMap, hash::Hash}; #[derive(Clone, Debug)] pub struct ExpirableEntry { @@ -14,53 +14,111 @@ pub struct ExpirableEntry { } impl ExpirableEntry { + #[inline(always)] + pub fn new(v: V, exp: Duration) -> Self { + Self { + expires_at: Instant::now() + exp, + value: v, + } + } + + #[inline(always)] pub fn get_element(&self) -> &V { &self.value } + #[inline(always)] + pub fn update_value(&mut self, v: V) { self.value = v } + + #[inline(always)] pub fn update_expiration(&mut self, expires_at: Instant) { self.expires_at = expires_at } + + /// Checks whether entry has longer ttl than the given one. + #[inline(always)] + pub fn has_longer_life_than(&self, min_ttl: Duration) -> bool { self.expires_at > Instant::now() + min_ttl } } -impl Default for ExpirableMap { +impl Default for ExpirableMap { fn default() -> Self { Self::new() } } /// A map that allows associating values with keys and expiring entries. -/// It is important to note that this implementation does not automatically -/// remove any entries; it is the caller's responsibility to invoke `clear_expired_entries` -/// at specified intervals. +/// It is important to note that this implementation does not have a background worker to +/// automatically clear expired entries. Outdated entries are only removed when the control flow +/// is handed back to the map mutably (i.e. some mutable method of the map is invoked). /// /// WARNING: This is designed for performance-oriented use-cases utilizing `FxHashMap` /// under the hood and is not suitable for cryptographic purposes. #[derive(Clone, Debug)] -pub struct ExpirableMap(FxHashMap>); +pub struct ExpirableMap { + map: FxHashMap>, + /// A sorted inverse map from expiration times to keys to speed up expired entries clearing. + expiries: BTreeMap, +} -impl ExpirableMap { +impl ExpirableMap { /// Creates a new empty `ExpirableMap` #[inline] - pub fn new() -> Self { Self(FxHashMap::default()) } + pub fn new() -> Self { + Self { + map: FxHashMap::default(), + expiries: BTreeMap::new(), + } + } - /// Returns the associated value if present. + /// Returns the associated value if present and not expired. #[inline] - pub fn get(&mut self, k: &K) -> Option<&V> { self.0.get(k).map(|v| &v.value) } + pub fn get(&self, k: &K) -> Option<&V> { + self.map + .get(k) + .filter(|v| v.expires_at > Instant::now()) + .map(|v| &v.value) + } + + /// Removes a key-value pair from the map and returns the associated value if present and not expired. + #[inline] + pub fn remove(&mut self, k: &K) -> Option { + self.map.remove(k).filter(|v| v.expires_at > Instant::now()).map(|v| { + self.expiries.remove(&v.expires_at); + v.value + }) + } /// Inserts a key-value pair with an expiration duration. /// /// If a value already exists for the given key, it will be updated and then /// the old one will be returned. pub fn insert(&mut self, k: K, v: V, exp: Duration) -> Option { - let entry = ExpirableEntry { - expires_at: Instant::now() + exp, - value: v, - }; + self.clear_expired_entries(); + let entry = ExpirableEntry::new(v, exp); + self.expiries.insert(entry.expires_at, k); + self.map.insert(k, entry).map(|v| v.value) + } - self.0.insert(k, entry).map(|v| v.value) + /// Clears the map. + pub fn clear(&mut self) { + self.map.clear(); + self.expiries.clear(); } /// Removes expired entries from the map. - pub fn clear_expired_entries(&mut self) { self.0.retain(|_k, v| Instant::now() < v.expires_at); } - - /// Removes a key-value pair from the map and returns the associated value if present. - #[inline] - pub fn remove(&mut self, k: &K) -> Option { self.0.remove(k).map(|v| v.value) } + /// + /// Iterates through the `expiries` in order, removing entries that have expired. + /// Stops at the first non-expired entry, leveraging the sorted nature of `BTreeMap`. + fn clear_expired_entries(&mut self) { + let now = Instant::now(); + + // `pop_first()` is used here as it efficiently removes expired entries. + // `first_key_value()` was considered as it wouldn't need re-insertion for + // non-expired entries, but it would require an extra remove operation for + // each expired entry. `pop_first()` needs only one re-insertion per call, + // which is an acceptable trade-off compared to multiple remove operations. + while let Some((exp, key)) = self.expiries.pop_first() { + if exp > now { + self.expiries.insert(exp, key); + break; + } + self.map.remove(&key); + } + } } #[cfg(any(test, target_arch = "wasm32"))] @@ -80,8 +138,8 @@ mod tests { let exp = Duration::from_secs(1); // Insert 2 entries with 1 sec expiration time - expirable_map.insert("key1".to_string(), value.to_string(), exp); - expirable_map.insert("key2".to_string(), value.to_string(), exp); + expirable_map.insert("key1", value, exp); + expirable_map.insert("key2", value, exp); // Wait for entries to expire Timer::sleep(2.).await; @@ -90,14 +148,14 @@ mod tests { expirable_map.clear_expired_entries(); // We waited for 2 seconds, so we shouldn't have any entry accessible - assert_eq!(expirable_map.0.len(), 0); + assert_eq!(expirable_map.map.len(), 0); // Insert 5 entries - expirable_map.insert("key1".to_string(), value.to_string(), Duration::from_secs(5)); - expirable_map.insert("key2".to_string(), value.to_string(), Duration::from_secs(4)); - expirable_map.insert("key3".to_string(), value.to_string(), Duration::from_secs(7)); - expirable_map.insert("key4".to_string(), value.to_string(), Duration::from_secs(2)); - expirable_map.insert("key5".to_string(), value.to_string(), Duration::from_millis(3750)); + expirable_map.insert("key1", value, Duration::from_secs(5)); + expirable_map.insert("key2", value, Duration::from_secs(4)); + expirable_map.insert("key3", value, Duration::from_secs(7)); + expirable_map.insert("key4", value, Duration::from_secs(2)); + expirable_map.insert("key5", value, Duration::from_millis(3750)); // Wait 2 seconds to expire some entries Timer::sleep(2.).await; @@ -106,6 +164,6 @@ mod tests { expirable_map.clear_expired_entries(); // We waited for 2 seconds, only one entry should expire - assert_eq!(expirable_map.0.len(), 4); + assert_eq!(expirable_map.map.len(), 4); }); } diff --git a/mm2src/common/jsonrpc_client.rs b/mm2src/common/jsonrpc_client.rs index 94a1ca809b..3f9e4cf6f6 100644 --- a/mm2src/common/jsonrpc_client.rs +++ b/mm2src/common/jsonrpc_client.rs @@ -2,7 +2,7 @@ use futures01::Future; use itertools::Itertools; use serde::de::DeserializeOwned; use serde_json::{self as json, Value as Json}; -use std::collections::{BTreeSet, HashMap}; +use std::collections::HashMap; use std::fmt; /// Macro generating functions for RPC requests. @@ -69,10 +69,10 @@ impl From for JsonRpcRemoteAddr { /// The identifier is designed to uniquely match outgoing requests and incoming responses. /// Even if the batch response is sorted in a different order, `BTreeSet` allows it to be matched to the request. -#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] +#[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] pub enum JsonRpcId { - Single(String), - Batch(BTreeSet), + Single(u64), + Batch(u64), } /// Serializable RPC request that is either single or batch. @@ -114,19 +114,15 @@ impl fmt::Debug for JsonRpcRequestEnum { pub struct JsonRpcRequest { pub jsonrpc: String, #[serde(default)] - pub id: String, + pub id: u64, pub method: String, pub params: Vec, } impl JsonRpcRequest { - // Returns [`JsonRpcRequest::id`]. - #[inline] - pub fn get_id(&self) -> &str { &self.id } - /// Returns a `JsonRpcId` identifier of the request. #[inline] - pub fn rpc_id(&self) -> JsonRpcId { JsonRpcId::Single(self.id.clone()) } + pub fn rpc_id(&self) -> JsonRpcId { JsonRpcId::Single(self.id) } } impl From for JsonRpcRequestEnum { @@ -140,7 +136,12 @@ pub struct JsonRpcBatchRequest(Vec); impl JsonRpcBatchRequest { /// Returns a `JsonRpcId` identifier of the request. #[inline] - pub fn rpc_id(&self) -> JsonRpcId { JsonRpcId::Batch(self.orig_sequence_ids().collect()) } + pub fn rpc_id(&self) -> JsonRpcId { + // This shouldn't be called on an empty batch, but let's + // simply set the batch ID to maximum if the batch is empty. + let batch_id = self.0.iter().map(|res| res.id).max().unwrap_or(u64::MAX); + JsonRpcId::Batch(batch_id) + } /// Returns the number of the requests in the batch. #[inline] @@ -153,7 +154,7 @@ impl JsonRpcBatchRequest { /// Returns original sequence of identifiers. /// The method is used to process batch responses in the same order in which the requests were sent. #[inline] - fn orig_sequence_ids(&self) -> impl Iterator + '_ { self.0.iter().map(|req| req.id.clone()) } + fn orig_sequence_ids(&self) -> impl Iterator + '_ { self.0.iter().map(|req| req.id) } } impl From for JsonRpcRequestEnum { @@ -185,7 +186,7 @@ pub struct JsonRpcResponse { #[serde(default)] pub jsonrpc: String, #[serde(default)] - pub id: String, + pub id: u64, #[serde(default)] pub result: Json, #[serde(default)] @@ -195,7 +196,7 @@ pub struct JsonRpcResponse { impl JsonRpcResponse { /// Returns a `JsonRpcId` identifier of the response. #[inline] - pub fn rpc_id(&self) -> JsonRpcId { JsonRpcId::Single(self.id.clone()) } + pub fn rpc_id(&self) -> JsonRpcId { JsonRpcId::Single(self.id) } } /// Deserializable RPC batch response. @@ -204,7 +205,12 @@ pub struct JsonRpcBatchResponse(Vec); impl JsonRpcBatchResponse { /// Returns a `JsonRpcId` identifier of the response. - pub fn rpc_id(&self) -> JsonRpcId { JsonRpcId::Batch(self.0.iter().map(|res| res.id.clone()).collect()) } + pub fn rpc_id(&self) -> JsonRpcId { + // This shouldn't be called on an empty batch, but let's + // simply set the batch ID to maximum if the batch is empty. + let batch_id = self.0.iter().map(|res| res.id).max().unwrap_or(u64::MAX); + JsonRpcId::Batch(batch_id) + } /// Returns the number of the requests in the batch. #[inline] @@ -272,8 +278,8 @@ pub trait JsonRpcClient { /// Returns a stringified version of the JSON-RPC protocol. fn version(&self) -> &'static str; - /// Returns a stringified identifier of the next request. - fn next_id(&self) -> String; + /// Returns a unique identifier for the next request. + fn next_id(&self) -> u64; /// Get info that is used in particular to supplement the error info fn client_info(&self) -> String; @@ -395,8 +401,7 @@ fn process_transport_batch_result( }; // Turn the vector of responses into a hashmap by their IDs to get quick access to the content of the responses. - let mut response_map: HashMap = - batch.into_iter().map(|res| (res.id.clone(), res)).collect(); + let mut response_map: HashMap<_, _> = batch.into_iter().map(|res| (res.id, res)).collect(); if response_map.len() != orig_ids.len() { return Err(JsonRpcErrorType::Parse( remote_addr, diff --git a/mm2src/common/log/native_log.rs b/mm2src/common/log/native_log.rs index 269b9fb28f..739b7d017e 100644 --- a/mm2src/common/log/native_log.rs +++ b/mm2src/common/log/native_log.rs @@ -4,7 +4,7 @@ use std::io::Write; use std::os::raw::c_char; use std::str::FromStr; -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] pub enum LogLevel { /// A level lower than all log levels. Off = 0, @@ -13,6 +13,7 @@ pub enum LogLevel { /// Corresponds to the `WARN` log level. Warn = 2, /// Corresponds to the `INFO` log level. + #[default] Info = 3, /// Corresponds to the `DEBUG` log level. Debug = 4, @@ -27,10 +28,6 @@ impl LogLevel { } } -impl Default for LogLevel { - fn default() -> Self { LogLevel::Info } -} - pub struct FfiCallback { cb_f: extern "C" fn(line: *const c_char), } diff --git a/mm2src/common/notifier.rs b/mm2src/common/notifier.rs new file mode 100644 index 0000000000..a82253b537 --- /dev/null +++ b/mm2src/common/notifier.rs @@ -0,0 +1,53 @@ +//! A simple notification system based on mpsc channels. +//! +//! Since this is based on mpsc, multiple notifiers (senders) are allowed while only a single +//! notifiee (receiver) listens for notifications. +//! +//! NOTE: This implementation memory leaks (in contrast to tokio's, but not used here to avoid tokio dependency on wasm). +//! This is because with each `clone()` of the sender we have a new slot in the channel (this is how `futures-rs` does mpsc). +//! These are removed when the receiver calls `wait()`, which calls `clear()`. But if the receiver never `wait()`s for any reason, +//! and there is a thread that doesn't stop `notify()`ing, the channel will keep growing unbounded. +//! +//! So one must make sure that either `wait()` is called after some time or the receiver is dropped when it's no longer needed. +use futures::{channel::mpsc, StreamExt}; + +#[derive(Clone, Debug)] +pub struct Notifier(mpsc::Sender<()>); + +#[derive(Debug)] +pub struct Notifiee(mpsc::Receiver<()>); + +impl Notifier { + /// Create a new notifier and notifiee pair. + pub fn new() -> (Notifier, Notifiee) { + let (sender, receiver) = mpsc::channel(0); + (Notifier(sender), Notifiee(receiver)) + } + + /// Notify the receiver. + /// + /// This will error if the receiver has been dropped (disconnected). + pub fn notify(&self) -> Result<(), &'static str> { + if let Err(e) = self.0.clone().try_send(()) { + if e.is_disconnected() { + return Err("Notification receiver has been dropped."); + } + } + Ok(()) + } +} + +impl Notifiee { + /// Wait for a notification from any notifier. + /// + /// This will error if all notifiers have been dropped (disconnected). + pub async fn wait(&mut self) -> Result<(), &'static str> { + let result = self.0.next().await.ok_or("All notifiers have been dropped."); + // Clear pending notifications if there are any, since we have already been notified. + self.clear(); + result + } + + /// Clears the pending notifications if there are any. + fn clear(&mut self) { while let Ok(Some(_)) = self.0.try_next() {} } +} diff --git a/mm2src/crypto/src/crypto_ctx.rs b/mm2src/crypto/src/crypto_ctx.rs index 92ac1f2196..ffc83603a6 100644 --- a/mm2src/crypto/src/crypto_ctx.rs +++ b/mm2src/crypto/src/crypto_ctx.rs @@ -316,10 +316,12 @@ impl CryptoCtx { *ctx_field = Some(result.clone()); drop(ctx_field); - ctx.rmd160.pin(rmd160).map_to_mm(CryptoInitError::Internal)?; + ctx.rmd160 + .set(rmd160) + .map_to_mm(|_| CryptoInitError::Internal("Already Initialized".to_string()))?; ctx.shared_db_id - .pin(shared_db_id) - .map_to_mm(CryptoInitError::Internal)?; + .set(shared_db_id) + .map_to_mm(|_| CryptoInitError::Internal("Already Initialized".to_string()))?; info!("Public key hash: {rmd160}"); info!("Shared Database ID: {shared_db_id}"); diff --git a/mm2src/db_common/src/sqlite.rs b/mm2src/db_common/src/sqlite.rs index 74ddae884d..f92707d2d5 100644 --- a/mm2src/db_common/src/sqlite.rs +++ b/mm2src/db_common/src/sqlite.rs @@ -310,7 +310,7 @@ where } /// As per https://twitter.com/marcan42/status/1494213862970707969, I've noticed significant SQLite performance -/// difference on M1 Mac and Linux. +/// difference on Apple Silicon Mac and Linux. /// But according to https://phiresky.github.io/blog/2020/sqlite-performance-tuning/, these pragmas should /// be safe to use, while giving great speed boost. /// With these, Mac and Linux have comparable SQLite performance. diff --git a/mm2src/floodsub/CHANGELOG.md b/mm2src/floodsub/CHANGELOG.md deleted file mode 100644 index b361796860..0000000000 --- a/mm2src/floodsub/CHANGELOG.md +++ /dev/null @@ -1,15 +0,0 @@ -# 0.22.0 [2020-09-09] - -- Update `libp2p-swarm` and `libp2p-core`. - -# 0.21.0 [2020-08-18] - -- Bump `libp2p-core` and `libp2p-swarm` dependency. - -# 0.20.0 [2020-07-01] - -- Updated dependencies. - -# 0.19.1 [2020-06-22] - -- Updated dependencies. diff --git a/mm2src/floodsub/Cargo.toml b/mm2src/floodsub/Cargo.toml deleted file mode 100644 index b54b60448c..0000000000 --- a/mm2src/floodsub/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "libp2p-floodsub" -edition = "2018" -description = "Floodsub protocol for libp2p" -version = "0.22.0" -authors = ["Parity Technologies "] -license = "MIT" -repository = "https://github.com/libp2p/rust-libp2p" -keywords = ["peer-to-peer", "libp2p", "networking"] -categories = ["network-programming", "asynchronous"] - -[lib] -doctest = false - -[dependencies] -cuckoofilter = "0.3.2" -futures = "0.3.1" -libp2p-core = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -libp2p-swarm = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -prost = "0.10" -rand = "0.7" -smallvec = "1.0" - -[build-dependencies] -prost-build = { version = "0.10.4", default-features = false } diff --git a/mm2src/floodsub/build.rs b/mm2src/floodsub/build.rs deleted file mode 100644 index 9671983699..0000000000 --- a/mm2src/floodsub/build.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -fn main() { prost_build::compile_protos(&["src/rpc.proto"], &["src"]).unwrap(); } diff --git a/mm2src/floodsub/src/layer.rs b/mm2src/floodsub/src/layer.rs deleted file mode 100644 index 7151948a95..0000000000 --- a/mm2src/floodsub/src/layer.rs +++ /dev/null @@ -1,401 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -use crate::protocol::{FloodsubMessage, FloodsubProtocol, FloodsubRpc, FloodsubSubscription, FloodsubSubscriptionAction}; -use crate::topic::Topic; -use crate::FloodsubConfig; -use cuckoofilter::CuckooFilter; -use libp2p_core::{connection::ConnectionId, ConnectedPoint, Multiaddr, PeerId}; -use libp2p_swarm::{IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, OneShotHandler, - PollParameters}; -use smallvec::SmallVec; -use std::collections::hash_map::{DefaultHasher, HashMap}; -use std::task::{Context, Poll}; -use std::{collections::VecDeque, iter}; - -/// Network behaviour that handles the floodsub protocol. -pub struct Floodsub { - /// Events that need to be yielded to the outside when polling. - events: - VecDeque>>, - - config: FloodsubConfig, - - /// List of peers the network is connected to, and the topics that they're subscribed to. - // TODO: filter out peers that don't support floodsub, so that we avoid hammering them with - // opened substreams - connected_peers: HashMap>, - - // List of topics we're subscribed to. Necessary to filter out messages that we receive - // erroneously. - subscribed_topics: SmallVec<[Topic; 16]>, - - // We keep track of the messages we received (in the format `hash(source ID, seq_no)`) so that - // we don't dispatch the same message twice if we receive it twice on the network. - received: CuckooFilter, -} - -impl Floodsub { - /// Creates a `Floodsub` with default configuration. - pub fn new(local_peer_id: PeerId, forward_messages: bool) -> Self { - Self::from_config(FloodsubConfig::new(local_peer_id, forward_messages)) - } - - /// Creates a `Floodsub` with the given configuration. - pub fn from_config(config: FloodsubConfig) -> Self { - Floodsub { - events: VecDeque::new(), - config, - connected_peers: HashMap::new(), - subscribed_topics: SmallVec::new(), - received: CuckooFilter::new(), - } - } - - /// Subscribes to a topic. - /// - /// Returns true if the subscription worked. Returns false if we were already subscribed. - pub fn subscribe(&mut self, topic: Topic) -> bool { - if self.subscribed_topics.iter().any(|t| t.id() == topic.id()) { - return false; - } - - for peer in self.connected_peers.keys() { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *peer, - handler: NotifyHandler::Any, - event: FloodsubRpc { - messages: Vec::new(), - subscriptions: vec![FloodsubSubscription { - topic: topic.clone(), - action: FloodsubSubscriptionAction::Subscribe, - }], - }, - }); - } - - self.subscribed_topics.push(topic); - true - } - - /// Unsubscribes from a topic. - /// - /// Note that this only requires the topic name. - /// - /// Returns true if we were subscribed to this topic. - pub fn unsubscribe(&mut self, topic: Topic) -> bool { - let pos = match self.subscribed_topics.iter().position(|t| *t == topic) { - Some(pos) => pos, - None => return false, - }; - - self.subscribed_topics.remove(pos); - - for peer in self.connected_peers.keys() { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *peer, - handler: NotifyHandler::Any, - event: FloodsubRpc { - messages: Vec::new(), - subscriptions: vec![FloodsubSubscription { - topic: topic.clone(), - action: FloodsubSubscriptionAction::Unsubscribe, - }], - }, - }); - } - - true - } - - /// Publishes a message to the network, if we're subscribed to the topic only. - pub fn publish(&mut self, topic: impl Into, data: impl Into>) { - self.publish_many(iter::once(topic), data) - } - - /// Publishes a message to the network, even if we're not subscribed to the topic. - pub fn publish_any(&mut self, topic: impl Into, data: impl Into>) { - self.publish_many_any(iter::once(topic), data) - } - - /// Publishes a message with multiple topics to the network. - /// - /// - /// > **Note**: Doesn't do anything if we're not subscribed to any of the topics. - pub fn publish_many(&mut self, topic: impl IntoIterator>, data: impl Into>) { - self.publish_many_inner(topic, data, true) - } - - /// Publishes a message with multiple topics to the network, even if we're not subscribed to any of the topics. - pub fn publish_many_any(&mut self, topic: impl IntoIterator>, data: impl Into>) { - self.publish_many_inner(topic, data, false) - } - - fn publish_many_inner( - &mut self, - topic: impl IntoIterator>, - data: impl Into>, - check_self_subscriptions: bool, - ) { - let message = FloodsubMessage { - source: self.config.local_peer_id, - data: data.into(), - // If the sequence numbers are predictable, then an attacker could flood the network - // with packets with the predetermined sequence numbers and absorb our legitimate - // messages. We therefore use a random number. - sequence_number: rand::random::<[u8; 20]>().to_vec(), - topics: topic.into_iter().map(Into::into).collect(), - }; - - let self_subscribed = self - .subscribed_topics - .iter() - .any(|t| message.topics.iter().any(|u| t == u)); - if self_subscribed { - self.received.add(&message); - if self.config.subscribe_local_messages { - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(FloodsubEvent::Message( - message.clone(), - ))); - } - } - // Don't publish the message if we have to check subscriptions - // and we're not subscribed ourselves to any of the topics. - if check_self_subscriptions && !self_subscribed { - return; - } - - // Send to peers we know are subscribed to the topic. - for (peer_id, sub_topic) in self.connected_peers.iter() { - if !sub_topic.iter().any(|t| message.topics.iter().any(|u| t == u)) { - continue; - } - - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *peer_id, - handler: NotifyHandler::Any, - event: FloodsubRpc { - subscriptions: Vec::new(), - messages: vec![message.clone()], - }, - }); - } - } -} - -impl NetworkBehaviour for Floodsub { - type ConnectionHandler = OneShotHandler; - type OutEvent = FloodsubEvent; - - fn new_handler(&mut self) -> Self::ConnectionHandler { Default::default() } - - fn addresses_of_peer(&mut self, _: &PeerId) -> Vec { Vec::new() } - - fn inject_connection_established( - &mut self, - id: &PeerId, - _: &ConnectionId, - _: &ConnectedPoint, - _: Option<&Vec>, - other_established: usize, - ) { - if other_established > 0 { - // We only care about the first time a peer connects. - return; - } - - // We need to send our subscriptions to the newly-connected node. - for topic in self.subscribed_topics.iter().cloned() { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *id, - handler: NotifyHandler::Any, - event: FloodsubRpc { - messages: Vec::new(), - subscriptions: vec![FloodsubSubscription { - topic, - action: FloodsubSubscriptionAction::Subscribe, - }], - }, - }); - } - - self.connected_peers.insert(*id, SmallVec::new()); - } - - fn inject_connection_closed( - &mut self, - id: &PeerId, - _: &ConnectionId, - _: &ConnectedPoint, - _: ::Handler, - remaining_established: usize, - ) { - if remaining_established > 0 { - // we only care about peer disconnections - return; - } - - let was_in = self.connected_peers.remove(id); - debug_assert!(was_in.is_some()); - } - - fn inject_event(&mut self, propagation_source: PeerId, _connection: ConnectionId, event: InnerMessage) { - // We ignore successful sends or timeouts. - let event = match event { - InnerMessage::Rx(event) => event, - InnerMessage::Sent => return, - }; - - // Update connected peers topics - for subscription in event.subscriptions { - let remote_peer_topics = self.connected_peers - .get_mut(&propagation_source) - .expect("connected_peers is kept in sync with the peers we are connected to; we are guaranteed to only receive events from connected peers; QED"); - match subscription.action { - FloodsubSubscriptionAction::Subscribe => { - if !remote_peer_topics.contains(&subscription.topic) { - remote_peer_topics.push(subscription.topic.clone()); - } - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(FloodsubEvent::Subscribed { - peer_id: propagation_source, - topic: subscription.topic, - })); - }, - FloodsubSubscriptionAction::Unsubscribe => { - if let Some(pos) = remote_peer_topics.iter().position(|t| t == &subscription.topic) { - remote_peer_topics.remove(pos); - } - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(FloodsubEvent::Unsubscribed { - peer_id: propagation_source, - topic: subscription.topic, - })); - }, - } - } - - // List of messages we're going to propagate on the network. - let mut rpcs_to_dispatch: Vec<(PeerId, FloodsubRpc)> = Vec::new(); - - for message in event.messages { - // Use `self.received` to skip the messages that we have already received in the past. - // Note that this can false positive. - if !self.received.test_and_add(&message) { - continue; - } - - // Add the message to be dispatched to the user. - if self - .subscribed_topics - .iter() - .any(|t| message.topics.iter().any(|u| t == u)) - { - let event = FloodsubEvent::Message(message.clone()); - self.events.push_back(NetworkBehaviourAction::GenerateEvent(event)); - } - - if self.config.forward_messages { - // Propagate the message to everyone else who is subscribed to any of the topics. - for (peer_id, subscr_topics) in self.connected_peers.iter() { - if peer_id == &propagation_source { - continue; - } - - if !subscr_topics.iter().any(|t| message.topics.iter().any(|u| t == u)) { - continue; - } - - if let Some(pos) = rpcs_to_dispatch.iter().position(|(p, _)| p == peer_id) { - rpcs_to_dispatch[pos].1.messages.push(message.clone()); - } else { - rpcs_to_dispatch.push((*peer_id, FloodsubRpc { - subscriptions: Vec::new(), - messages: vec![message.clone()], - })); - } - } - } - } - - for (peer_id, rpc) in rpcs_to_dispatch { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::Any, - event: rpc, - }); - } - } - - fn poll( - &mut self, - _: &mut Context<'_>, - _: &mut impl PollParameters, - ) -> Poll> { - if let Some(event) = self.events.pop_front() { - return Poll::Ready(event); - } - - Poll::Pending - } -} - -/// Transmission between the `OneShotHandler` and the `FloodsubHandler`. -#[derive(Debug)] -pub enum InnerMessage { - /// We received an RPC from a remote. - Rx(FloodsubRpc), - /// We successfully sent an RPC request. - Sent, -} - -impl From for InnerMessage { - #[inline] - fn from(rpc: FloodsubRpc) -> InnerMessage { InnerMessage::Rx(rpc) } -} - -impl From<()> for InnerMessage { - #[inline] - fn from(_: ()) -> InnerMessage { InnerMessage::Sent } -} - -/// Event that can happen on the floodsub behaviour. -#[derive(Debug)] -pub enum FloodsubEvent { - /// A message has been received. - Message(FloodsubMessage), - - /// A remote subscribed to a topic. - Subscribed { - /// Remote that has subscribed. - peer_id: PeerId, - /// The topic it has subscribed to. - topic: Topic, - }, - - /// A remote unsubscribed from a topic. - Unsubscribed { - /// Remote that has unsubscribed. - peer_id: PeerId, - /// The topic it has subscribed from. - topic: Topic, - }, -} diff --git a/mm2src/floodsub/src/lib.rs b/mm2src/floodsub/src/lib.rs deleted file mode 100644 index 90592a9fe5..0000000000 --- a/mm2src/floodsub/src/lib.rs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -//! Implements the floodsub protocol, see also the: -//! [spec](https://github.com/libp2p/specs/tree/master/pubsub). -//! The implementation is customized for AtomicDEX purposes, all peers are considered as "target_peers" -//! So "target_peers" are removed from Floodsub behaviour - -use libp2p_core::PeerId; - -pub mod protocol; - -mod layer; -mod topic; - -mod rpc_proto { - include!(concat!(env!("OUT_DIR"), "/floodsub.pb.rs")); -} - -pub use self::layer::{Floodsub, FloodsubEvent}; -pub use self::protocol::{FloodsubMessage, FloodsubRpc}; -pub use self::topic::Topic; - -/// Configuration options for the Floodsub protocol. -pub struct FloodsubConfig { - /// Peer id of the local node. Used for the source of the messages that we publish. - pub local_peer_id: PeerId, - - /// `true` if messages published by local node should be propagated as messages received from - /// the network, `false` by default. - pub subscribe_local_messages: bool, - - pub forward_messages: bool, -} - -impl FloodsubConfig { - pub fn new(local_peer_id: PeerId, forward_messages: bool) -> Self { - Self { - local_peer_id, - subscribe_local_messages: false, - forward_messages, - } - } -} diff --git a/mm2src/floodsub/src/protocol.rs b/mm2src/floodsub/src/protocol.rs deleted file mode 100644 index bf24053ffb..0000000000 --- a/mm2src/floodsub/src/protocol.rs +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -use crate::rpc_proto; -use crate::topic::Topic; -use futures::{io::{AsyncRead, AsyncWrite}, - Future}; -use libp2p_core::{upgrade, InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo}; -use prost::Message; -use std::{error, fmt, io, iter, pin::Pin}; - -/// Implementation of `ConnectionUpgrade` for the floodsub protocol. -#[derive(Debug, Clone, Default)] -pub struct FloodsubProtocol {} - -impl FloodsubProtocol { - /// Builds a new `FloodsubProtocol`. - pub fn new() -> FloodsubProtocol { FloodsubProtocol {} } -} - -impl UpgradeInfo for FloodsubProtocol { - type Info = &'static [u8]; - type InfoIter = iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { iter::once(b"/floodsub/1.0.0") } -} - -type PinBoxTryFut = Pin> + Send>>; - -impl InboundUpgrade for FloodsubProtocol -where - TSocket: AsyncRead + AsyncWrite + Send + Unpin + 'static, -{ - type Output = FloodsubRpc; - type Error = FloodsubDecodeError; - type Future = PinBoxTryFut; - - fn upgrade_inbound(self, mut socket: TSocket, _: Self::Info) -> Self::Future { - Box::pin(async move { - let packet = upgrade::read_length_prefixed(&mut socket, 2048).await?; - let rpc = rpc_proto::Rpc::decode(&packet[..])?; - - let mut messages = Vec::with_capacity(rpc.publish.len()); - for publish in rpc.publish.into_iter() { - messages.push(FloodsubMessage { - source: PeerId::from_bytes(&publish.from.unwrap_or_default()) - .map_err(|_| FloodsubDecodeError::InvalidPeerId)?, - data: publish.data.unwrap_or_default(), - sequence_number: publish.seqno.unwrap_or_default(), - topics: publish.topic_ids.into_iter().map(Topic::new).collect(), - }); - } - - Ok(FloodsubRpc { - messages, - subscriptions: rpc - .subscriptions - .into_iter() - .map(|sub| FloodsubSubscription { - action: if Some(true) == sub.subscribe { - FloodsubSubscriptionAction::Subscribe - } else { - FloodsubSubscriptionAction::Unsubscribe - }, - topic: Topic::new(sub.topic_id.unwrap_or_default()), - }) - .collect(), - }) - }) - } -} - -/// Reach attempt interrupt errors. -#[derive(Debug)] -pub enum FloodsubDecodeError { - /// Error when reading the packet from the socket. - ReadError(io::Error), - /// Error when decoding the raw buffer into a protobuf. - ProtobufError(prost::DecodeError), - /// Error when parsing the `PeerId` in the message. - InvalidPeerId, -} - -impl From for FloodsubDecodeError { - fn from(err: io::Error) -> Self { FloodsubDecodeError::ReadError(err) } -} - -impl From for FloodsubDecodeError { - fn from(err: prost::DecodeError) -> Self { FloodsubDecodeError::ProtobufError(err) } -} - -impl fmt::Display for FloodsubDecodeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - FloodsubDecodeError::ReadError(ref err) => write!(f, "Error while reading from socket: {}", err), - FloodsubDecodeError::ProtobufError(ref err) => write!(f, "Error while decoding protobuf: {}", err), - FloodsubDecodeError::InvalidPeerId => write!(f, "Error while decoding PeerId from message"), - } - } -} - -impl error::Error for FloodsubDecodeError { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match *self { - FloodsubDecodeError::ReadError(ref err) => Some(err), - FloodsubDecodeError::ProtobufError(ref err) => Some(err), - FloodsubDecodeError::InvalidPeerId => None, - } - } -} - -/// An RPC received by the floodsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FloodsubRpc { - /// List of messages that were part of this RPC query. - pub messages: Vec, - /// List of subscriptions. - pub subscriptions: Vec, -} - -impl UpgradeInfo for FloodsubRpc { - type Info = &'static [u8]; - type InfoIter = iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { iter::once(b"/floodsub/1.0.0") } -} - -impl OutboundUpgrade for FloodsubRpc -where - TSocket: AsyncWrite + AsyncRead + Send + Unpin + 'static, -{ - type Output = (); - type Error = io::Error; - type Future = PinBoxTryFut; - - fn upgrade_outbound(self, mut socket: TSocket, _: Self::Info) -> Self::Future { - Box::pin(async move { - let bytes = self.into_bytes(); - upgrade::write_length_prefixed(&mut socket, bytes).await?; - Ok(()) - }) - } -} - -impl FloodsubRpc { - /// Turns this `FloodsubRpc` into a message that can be sent to a substream. - fn into_bytes(self) -> Vec { - let rpc = rpc_proto::Rpc { - publish: self - .messages - .into_iter() - .map(|msg| rpc_proto::Message { - from: Some(msg.source.to_bytes()), - data: Some(msg.data), - seqno: Some(msg.sequence_number), - topic_ids: msg.topics.into_iter().map(|topic| topic.into()).collect(), - }) - .collect(), - - subscriptions: self - .subscriptions - .into_iter() - .map(|topic| rpc_proto::rpc::SubOpts { - subscribe: Some(topic.action == FloodsubSubscriptionAction::Subscribe), - topic_id: Some(topic.topic.into()), - }) - .collect(), - }; - - let mut buf = Vec::with_capacity(rpc.encoded_len()); - rpc.encode(&mut buf).expect("Vec provides capacity as needed"); - buf - } -} - -/// A message received by the floodsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FloodsubMessage { - /// Id of the peer that published this message. - pub source: PeerId, - - /// Content of the message. Its meaning is out of scope of this library. - pub data: Vec, - - /// An incrementing sequence number. - pub sequence_number: Vec, - - /// List of topics this message belongs to. - /// - /// Each message can belong to multiple topics at once. - pub topics: Vec, -} - -/// A subscription received by the floodsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FloodsubSubscription { - /// Action to perform. - pub action: FloodsubSubscriptionAction, - /// The topic from which to subscribe or unsubscribe. - pub topic: Topic, -} - -/// Action that a subscription wants to perform. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum FloodsubSubscriptionAction { - /// The remote wants to subscribe to the given topic. - Subscribe, - /// The remote wants to unsubscribe from the given topic. - Unsubscribe, -} diff --git a/mm2src/floodsub/src/rpc.proto b/mm2src/floodsub/src/rpc.proto deleted file mode 100644 index 84f0ea5179..0000000000 --- a/mm2src/floodsub/src/rpc.proto +++ /dev/null @@ -1,20 +0,0 @@ -syntax = "proto2"; - -package floodsub.pb; - -message RPC { - repeated SubOpts subscriptions = 1; - repeated Message publish = 2; - - message SubOpts { - optional bool subscribe = 1; // subscribe or unsubcribe - optional string topic_id = 2; - } -} - -message Message { - optional bytes from = 1; - optional bytes data = 2; - optional bytes seqno = 3; - repeated string topic_ids = 4; -} diff --git a/mm2src/floodsub/src/topic.rs b/mm2src/floodsub/src/topic.rs deleted file mode 100644 index 41d2b253b8..0000000000 --- a/mm2src/floodsub/src/topic.rs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -/// Built topic. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Topic(String); - -impl Topic { - /// Returns the id of the topic. - #[inline] - pub fn id(&self) -> &str { &self.0 } - - pub fn new(name: S) -> Topic - where - S: Into, - { - Topic(name.into()) - } -} - -impl From for String { - fn from(topic: Topic) -> String { topic.0 } -} diff --git a/mm2src/gossipsub/CHANGELOG.md b/mm2src/gossipsub/CHANGELOG.md deleted file mode 100644 index d2a5c025a7..0000000000 --- a/mm2src/gossipsub/CHANGELOG.md +++ /dev/null @@ -1,11 +0,0 @@ -# 0.20.0 [2020-07-01] - -- Updated dependencies. - -# 0.19.3 [2020-06-23] - -- Maintenance release fixing linter warnings. - -# 0.19.2 [2020-06-22] - -- Updated dependencies. diff --git a/mm2src/gossipsub/Cargo.toml b/mm2src/gossipsub/Cargo.toml deleted file mode 100644 index b34f1911a3..0000000000 --- a/mm2src/gossipsub/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "atomicdex-gossipsub" -edition = "2018" -description = "Gossipsub protocol for AtomicDEX, based on libp2p gossipsub" -version = "0.20.0" -authors = ["Age Manning "] -license = "MIT" -repository = "https://github.com/libp2p/rust-libp2p" -keywords = ["peer-to-peer", "libp2p", "networking"] -categories = ["network-programming", "asynchronous"] - -[lib] -doctest = false - -[dependencies] -base64 = "0.21.2" -bytes = "0.5.4" -byteorder = "1.3.2" -common = { path = "../common" } -fnv = "1.0.6" -futures = "0.3.1" -futures_codec = "0.4.0" -libp2p-swarm = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -libp2p-core = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -log = "0.4.17" -prost = "0.10" -rand = "0.7" -sha2 = "0.10" -smallvec = "1.1.0" -unsigned-varint = { version = "0.4.0", features = ["futures-codec"] } -wasm-timer = "0.2.4" - -[dev-dependencies] -async-std = "1.6.2" -env_logger = "0.9.3" -libp2p-plaintext = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -libp2p-yamux = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -quickcheck= { version = "0.9.2", default-features = false } - -[build-dependencies] -prost-build = { version = "0.10.4", default-features = false } diff --git a/mm2src/gossipsub/build.rs b/mm2src/gossipsub/build.rs deleted file mode 100644 index 9671983699..0000000000 --- a/mm2src/gossipsub/build.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -fn main() { prost_build::compile_protos(&["src/rpc.proto"], &["src"]).unwrap(); } diff --git a/mm2src/gossipsub/src/behaviour.rs b/mm2src/gossipsub/src/behaviour.rs deleted file mode 100644 index 19eb7a2627..0000000000 --- a/mm2src/gossipsub/src/behaviour.rs +++ /dev/null @@ -1,1526 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -use crate::config::GossipsubConfig; -use crate::handler::GossipsubHandler; -use crate::mcache::MessageCache; -use crate::protocol::{GossipsubControlAction, GossipsubMessage, GossipsubSubscription, GossipsubSubscriptionAction, - MessageId}; -use crate::topic::{Topic, TopicHash}; -use common::time_cache::{Entry as TimeCacheEntry, TimeCache}; -use futures::prelude::*; -use libp2p_core::{connection::ConnectionId, ConnectedPoint, Multiaddr, PeerId}; -use libp2p_swarm::{IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters}; -use log::{debug, error, info, trace, warn}; -use rand::seq::SliceRandom; -use smallvec::SmallVec; -use std::collections::hash_map::Entry; -use std::collections::{HashMap, HashSet, VecDeque}; -use std::iter; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::time::Duration; -use wasm_timer::{Instant, Interval}; - -mod tests; - -/// Network behaviour that handles the gossipsub protocol. -pub struct Gossipsub { - /// Configuration providing gossipsub performance parameters. - config: GossipsubConfig, - - /// Events that need to be yielded to the outside when polling. - events: VecDeque>>, - - /// Pools non-urgent control messages between heartbeats. - control_pool: HashMap>, - - /// Peer id of the local node. Used for the source of the messages that we publish. - local_peer_id: PeerId, - - /// A map of all connected peers - A map of topic hash to a list of gossipsub peer Ids. - topic_peers: HashMap>, - - /// A map of all connected peers to their subscribed topics. - peer_topics: HashMap>, - - /// The peer ids of connected relay nodes - connected_relays: HashSet, - - /// relays to which we forward the messages. Also tracks the relay mesh size of nodes in mesh. - relays_mesh: HashMap, - - /// Peers included our node to their relays mesh - included_to_relays_mesh: HashSet, - - /// Overlay network of connected peers - Maps topics to connected gossipsub peers. - mesh: HashMap>, - - /// Map of topics to list of peers that we publish to, but don't subscribe to. - fanout: HashMap>, - - /// The last publish time for fanout topics. - fanout_last_pub: HashMap, - - /// Message cache for the last few heartbeats. - mcache: MessageCache, - - /// We keep track of the messages we received (in the format `string(source ID, seq_no)`) so that - /// we don't dispatch the same message twice if we receive it twice on the network. - /// Also store the peers from which message was received so we don't manually propagate already known message to them - received: TimeCache>, - - /// Heartbeat interval stream. - heartbeat: Interval, - - /// Relay mesh maintenance interval stream. - relay_mesh_maintenance_interval: Interval, - - peer_connections: HashMap>, - - connected_addresses: Vec, - - /// The relay list which are forcefully kept in relay mesh - explicit_relay_list: Vec, -} - -impl Gossipsub { - /// Creates a `Gossipsub` struct given a set of parameters specified by `gs_config`. - pub fn new(local_peer_id: PeerId, gs_config: GossipsubConfig) -> Self { - let local_peer_id = if gs_config.no_source_id { - PeerId::from_bytes(&crate::config::IDENTITY_SOURCE).expect("Valid peer id") - } else { - local_peer_id - }; - - Gossipsub { - config: gs_config.clone(), - events: VecDeque::new(), - control_pool: HashMap::new(), - local_peer_id, - topic_peers: HashMap::new(), - peer_topics: HashMap::new(), - mesh: HashMap::new(), - fanout: HashMap::new(), - fanout_last_pub: HashMap::new(), - mcache: MessageCache::new( - gs_config.history_gossip, - gs_config.history_length, - gs_config.message_id_fn, - ), - received: TimeCache::new(gs_config.duplicate_cache_time), - heartbeat: Interval::new_at( - Instant::now() + gs_config.heartbeat_initial_delay, - gs_config.heartbeat_interval, - ), - relay_mesh_maintenance_interval: Interval::new_at( - Instant::now() + Duration::from_secs(10), - Duration::from_secs(10), - ), - peer_connections: HashMap::new(), - connected_relays: HashSet::new(), - relays_mesh: HashMap::new(), - included_to_relays_mesh: HashSet::new(), - connected_addresses: Vec::new(), - explicit_relay_list: Vec::new(), - } - } - - pub fn add_explicit_relay(&mut self, peer_id: PeerId) { - info!("Adding peer {} to explicit relay list", peer_id); - self.explicit_relay_list.push(peer_id); - } - - /// Subscribe to a topic. - /// - /// Returns true if the subscription worked. Returns false if we were already subscribed. - pub fn subscribe(&mut self, topic: Topic) -> bool { - debug!("Subscribing to topic: {}", topic); - if self.config.i_am_relay { - debug!("Relay is subscribed to all topics by default. Subscribe has no effect."); - return false; - } - let topic_hash = self.topic_hash(topic.clone()); - if self.mesh.get(&topic_hash).is_some() { - debug!("Topic: {} is already in the mesh.", topic); - return false; - } - - let peers: Vec<_> = self.peer_topics.keys().copied().collect(); - for peer_id in peers { - let mut fixed_event = None; // initialise the event once if needed - if fixed_event.is_none() { - fixed_event = Some(Arc::new(GossipsubRpc { - messages: Vec::new(), - subscriptions: vec![GossipsubSubscription { - topic_hash: topic_hash.clone(), - action: GossipsubSubscriptionAction::Subscribe, - }], - control_msgs: Vec::new(), - })); - } - - let event = fixed_event.expect("event has been initialised"); - - debug!("Sending SUBSCRIBE to peer: {:?}", peer_id); - self.notify_primary(peer_id, event); - } - - // call JOIN(topic) - // this will add new peers to the mesh for the topic - self.join(&topic_hash); - info!("Subscribed to topic: {}", topic); - true - } - - pub fn is_subscribed(&self, topic_hash: &TopicHash) -> bool { self.mesh.contains_key(topic_hash) } - - /// Unsubscribes from a topic. - /// - /// Returns true if we were subscribed to this topic. - pub fn unsubscribe(&mut self, topic: Topic) -> bool { - debug!("Unsubscribing from topic: {}", topic); - if self.config.i_am_relay { - debug!("Relay is subscribed to all topics by default. Unsubscribe has no effect"); - return false; - } - let topic_hash = &self.topic_hash(topic); - - if self.mesh.get(topic_hash).is_none() { - debug!("Already unsubscribed from topic: {:?}", topic_hash); - // we are not subscribed - return false; - } - - // announce to all peers in the topic - let mut fixed_event = None; // initialise the event once if needed - if let Some(peer_list) = self.topic_peers.get(topic_hash) { - if fixed_event.is_none() { - fixed_event = Some(Arc::new(GossipsubRpc { - messages: Vec::new(), - subscriptions: vec![GossipsubSubscription { - topic_hash: topic_hash.clone(), - action: GossipsubSubscriptionAction::Unsubscribe, - }], - control_msgs: Vec::new(), - })); - } - - let event = fixed_event.expect("event has been initialised"); - - for peer in peer_list.clone() { - debug!("Sending UNSUBSCRIBE to peer: {:?}", peer); - self.notify_primary(peer, event.clone()); - } - } - - // call LEAVE(topic) - // this will remove the topic from the mesh - self.leave(topic_hash); - - info!("Unsubscribed from topic: {:?}", topic_hash); - true - } - - /// Publishes a message to the network. - pub fn publish(&mut self, topic: &Topic, data: impl Into>) { - self.publish_many_from(iter::once(topic.clone()), data, self.local_peer_id) - } - - /// Publishes a message with multiple topics to the network. - pub fn publish_many(&mut self, topic: impl IntoIterator, data: impl Into>) { - self.publish_many_from(topic, data, self.local_peer_id); - } - - /// Publishes a message with multiple topics to the network. - pub fn publish_many_from( - &mut self, - topic: impl IntoIterator, - data: impl Into>, - source: PeerId, - ) { - let message = GossipsubMessage { - source, - data: data.into(), - // To be interoperable with the go-implementation this is treated as a 64-bit - // big-endian uint. - sequence_number: rand::random(), - topics: topic.into_iter().map(|t| self.topic_hash(t)).collect(), - }; - - debug!("Publishing message: {:?}", (self.config.message_id_fn)(&message)); - - // forward the message to mesh peers - self.forward_msg(message.clone(), &source); - - let mut recipient_peers = HashSet::new(); - for topic_hash in &message.topics { - // if not subscribed to the topic, use fanout peers - if self.mesh.get(topic_hash).is_none() { - debug!("Topic: {:?} not in the mesh", topic_hash); - // build a list of peers to forward the message to - // if we have fanout peers add them to the map - if self.fanout.contains_key(topic_hash) { - for peer in self.fanout.get(topic_hash).expect("Topic must exist") { - recipient_peers.insert(*peer); - } - } else { - // we have no fanout peers, select mesh_n of them and add them to the fanout - let mesh_n = self.config.mesh_n; - let new_peers = Self::get_random_peers(&self.topic_peers, topic_hash, mesh_n, |_| true); - // add the new peers to the fanout and recipient peers - self.fanout.insert(topic_hash.clone(), new_peers.clone()); - for peer in new_peers { - debug!("Peer added to fanout: {:?}", peer); - recipient_peers.insert(peer); - } - } - // we are publishing to fanout peers - update the time we published - self.fanout_last_pub.insert(topic_hash.clone(), Instant::now()); - } - } - - // add published message to our received caches - let msg_id = (self.config.message_id_fn)(&message); - self.mcache.put(message.clone()); - self.received.insert(msg_id.clone(), SmallVec::from_elem(source, 1)); - - debug!("Published message: {:?}", msg_id); - - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: vec![message], - control_msgs: Vec::new(), - }); - // Send to peers we know are subscribed to the topic. - for peer_id in recipient_peers.iter() { - debug!("Sending message to peer: {:?}", peer_id); - self.notify_primary(*peer_id, event.clone()); - } - } - - /// This function should be called when `config.manual_propagation` is `true` in order to - /// propagate messages. Messages are stored in the ['Memcache'] and validation is expected to be - /// fast enough that the messages should still exist in the cache. - /// - /// Calling this function will propagate a message stored in the cache, if it still exists. - /// If the message still exists in the cache, it will be forwarded and this function will return true, - /// otherwise it will return false. - pub fn propagate_message(&mut self, message_id: &MessageId, propagation_source: &PeerId) -> bool { - let message = match self.mcache.get(message_id) { - Some(message) => message.clone(), - None => { - warn!( - "Message not in cache. Ignoring forwarding. Message Id: {}", - message_id.0 - ); - return false; - }, - }; - self.forward_msg(message, propagation_source); - true - } - - /// Gossipsub JOIN(topic) - adds topic peers to mesh and sends them GRAFT messages. - fn join(&mut self, topic_hash: &TopicHash) { - debug!("Running JOIN for topic: {:?}", topic_hash); - - // if we are already in the mesh, return - if self.mesh.contains_key(topic_hash) { - info!("JOIN: The topic is already in the mesh, ignoring JOIN"); - return; - } - - let mut added_peers = vec![]; - - // check if we have mesh_n peers in fanout[topic] and add them to the mesh if we do, - // removing the fanout entry. - if let Some((_, peers)) = self.fanout.remove_entry(topic_hash) { - debug!("JOIN: Removing peers from the fanout for topic: {:?}", topic_hash); - // add up to mesh_n of them them to the mesh - // Note: These aren't randomly added, currently FIFO - let add_peers = std::cmp::min(peers.len(), self.config.mesh_n); - debug!( - "JOIN: Adding {:?} peers from the fanout for topic: {:?}", - add_peers, topic_hash - ); - added_peers.extend_from_slice(&peers[..add_peers]); - self.mesh.insert(topic_hash.clone(), peers[..add_peers].to_vec()); - // remove the last published time - self.fanout_last_pub.remove(topic_hash); - } - - // check if we need to get more peers, which we randomly select - if added_peers.len() < self.config.mesh_n { - // get the peers - let new_peers = Self::get_random_peers( - &self.topic_peers, - topic_hash, - self.config.mesh_n - added_peers.len(), - |_| true, - ); - added_peers.extend_from_slice(&new_peers); - // add them to the mesh - debug!("JOIN: Inserting {:?} random peers into the mesh", new_peers.len()); - let mesh_peers = self.mesh.entry(topic_hash.clone()).or_insert_with(Vec::new); - mesh_peers.extend_from_slice(&new_peers); - } - - for peer_id in added_peers { - // Send a GRAFT control message - info!("JOIN: Sending Graft message to peer: {:?}", peer_id); - Self::control_pool_add(&mut self.control_pool, peer_id, GossipsubControlAction::Graft { - topic_hash: topic_hash.clone(), - }); - } - debug!("Completed JOIN for topic: {:?}", topic_hash); - } - - /// Gossipsub LEAVE(topic) - Notifies mesh\[topic\] peers with PRUNE messages. - fn leave(&mut self, topic_hash: &TopicHash) { - debug!("Running LEAVE for topic {:?}", topic_hash); - - // if our mesh contains the topic, send prune to peers and delete it from the mesh - if let Some((_, peers)) = self.mesh.remove_entry(topic_hash) { - for peer in peers { - // Send a PRUNE control message - info!("LEAVE: Sending PRUNE to peer: {:?}", peer); - Self::control_pool_add(&mut self.control_pool, peer, GossipsubControlAction::Prune { - topic_hash: topic_hash.clone(), - }); - } - } - debug!("Completed LEAVE for topic: {:?}", topic_hash); - } - - /// Handles an IHAVE control message. Checks our cache of messages. If the message is unknown, - /// requests it with an IWANT control message. - fn handle_ihave(&mut self, peer_id: &PeerId, ihave_msgs: Vec<(TopicHash, Vec)>) { - debug!("Handling IHAVE for peer: {:?}", peer_id); - // use a hashset to avoid duplicates efficiently - let mut iwant_ids = HashSet::new(); - - for (topic, ids) in ihave_msgs { - // only process the message if we are subscribed - if !self.mesh.contains_key(&topic) { - debug!("IHAVE: Ignoring IHAVE - Not subscribed to topic: {:?}", topic); - continue; - } - - for id in ids { - if !self.received.contains_key(&id) { - // have not seen this message, request it - iwant_ids.insert(id); - } - } - } - - if !iwant_ids.is_empty() { - // Send the list of IWANT control messages - debug!("IHAVE: Sending IWANT message"); - Self::control_pool_add(&mut self.control_pool, *peer_id, GossipsubControlAction::IWant { - message_ids: iwant_ids.iter().cloned().collect(), - }); - } - debug!("Completed IHAVE handling for peer: {:?}", peer_id); - } - - /// Handles an IWANT control message. Checks our cache of messages. If the message exists it is - /// forwarded to the requesting peer. - fn handle_iwant(&mut self, peer_id: &PeerId, iwant_msgs: Vec) { - debug!("Handling IWANT for peer: {:?}", peer_id); - // build a hashmap of available messages - let mut cached_messages = HashMap::new(); - - for id in iwant_msgs { - // if we have it, add it do the cached_messages mapping - if let Some(msg) = self.mcache.get(&id) { - cached_messages.insert(id.clone(), msg.clone()); - } - } - - if !cached_messages.is_empty() { - debug!("IWANT: Sending cached messages to peer: {:?}", peer_id); - // Send the messages to the peer - let message_list = cached_messages.into_iter().map(|entry| entry.1).collect(); - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: message_list, - control_msgs: Vec::new(), - }); - self.notify_primary(*peer_id, event); - } - debug!("Completed IWANT handling for peer: {:?}", peer_id); - } - - /// Handles GRAFT control messages. If subscribed to the topic, adds the peer to mesh, if not, - /// responds with PRUNE messages. - fn handle_graft(&mut self, peer_id: &PeerId, topics: Vec) { - debug!("Handling GRAFT message for peer: {:?}", peer_id); - - let mut to_prune_topics = HashSet::new(); - for topic_hash in topics { - if let Some(peers) = self.mesh.get_mut(&topic_hash) { - // if we are subscribed, add peer to the mesh, if not already added - info!( - "GRAFT: Mesh link added for peer: {:?} in topic: {:?}", - peer_id, topic_hash - ); - // ensure peer is not already added - if !peers.contains(peer_id) { - peers.push(*peer_id); - } - } else { - to_prune_topics.insert(topic_hash.clone()); - } - } - - if !to_prune_topics.is_empty() { - // build the prune messages to send - let prune_messages = to_prune_topics - .iter() - .map(|t| GossipsubControlAction::Prune { topic_hash: t.clone() }) - .collect(); - // Send the prune messages to the peer - info!( - "GRAFT: Not subscribed to topics - Sending PRUNE to peer: {:?}", - peer_id - ); - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: prune_messages, - }); - self.notify_primary(*peer_id, event); - } - debug!("Completed GRAFT handling for peer: {:?}", peer_id); - } - - /// Handles PRUNE control messages. Removes peer from the mesh. - fn handle_prune(&mut self, peer_id: &PeerId, topics: Vec) { - debug!("Handling PRUNE message for peer: {:?}", peer_id); - for topic_hash in topics { - if let Some(peers) = self.mesh.get_mut(&topic_hash) { - // remove the peer if it exists in the mesh - info!( - "PRUNE: Removing peer: {:?} from the mesh for topic: {:?}", - peer_id, topic_hash - ); - peers.retain(|p| p != peer_id); - } - } - debug!("Completed PRUNE handling for peer: {:?}", peer_id); - } - - /// Handles IAmrelay control message, does nothing if remote peer already subscribed to some topic - fn handle_i_am_relay(&mut self, peer_id: &PeerId, is_relay: bool) { - debug!("Handling IAmrelay message for peer: {:?}", peer_id); - if self.peer_topics.entry(*peer_id).or_insert_with(Vec::new).is_empty() && is_relay { - info!("IAmrelay: Adding peer: {:?} to the relays list", peer_id); - self.connected_relays.insert(*peer_id); - if self.relays_mesh.len() < self.config.mesh_n_low { - info!("IAmrelay: Adding peer: {:?} to the relay mesh", peer_id); - self.add_peers_to_relays_mesh(vec![*peer_id]); - } - } - debug!("Completed IAmrelay handling for peer: {:?}", peer_id); - } - - /// Handles IncludedTorelaysMesh message - fn handle_included_to_relays_mesh(&mut self, peer_id: &PeerId, is_included: bool, other_mesh_size: usize) { - if self.is_relay() { - debug!( - "Handling IncludedTorelaysMesh message for peer: {:?}, is_included: {}", - peer_id, is_included - ); - if is_included { - if self.connected_relays.contains(peer_id) { - if self.relays_mesh.len() > self.config.mesh_n_high { - self.notify_excluded_from_relay_mesh(*peer_id); - } else { - debug!("Adding peer {:?} to relays_mesh", peer_id); - self.relays_mesh.insert(*peer_id, other_mesh_size); - } - } else { - debug!("Adding peer {:?} to included_to_relays_mesh", peer_id); - self.included_to_relays_mesh.insert(*peer_id); - } - } else { - debug!( - "Removing peer {:?} from included_to_relays_mesh and relays mesh", - peer_id - ); - self.included_to_relays_mesh.remove(peer_id); - self.relays_mesh.remove(peer_id); - } - } else { - debug!( - "Ignoring IncludedTorelaysMesh message for peer: {:?}, is_included: {}", - peer_id, is_included - ); - } - } - - /// Handles a newly received GossipsubMessage. - /// Forwards the message to all peers in the mesh. - fn handle_received_message(&mut self, msg: GossipsubMessage, propagation_source: &PeerId) { - let msg_id = (self.config.message_id_fn)(&msg); - debug!("Handling message: {:?} from peer: {:?}", msg_id, propagation_source); - match self.received.entry(msg_id.clone()) { - TimeCacheEntry::Occupied(entry) => { - debug!("Message already received, ignoring. Message: {:?}", msg_id); - entry.into_mut().push(*propagation_source); - return; - }, - TimeCacheEntry::Vacant(entry) => { - entry.insert(SmallVec::from_elem(*propagation_source, 1)); - }, - } - // add to the memcache - self.mcache.put(msg.clone()); - - // dispatch the message to the user - debug!("Sending received message to user"); - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(GossipsubEvent::Message( - *propagation_source, - msg_id, - msg.clone(), - ))); - - // forward the message to mesh peers, if no validation is required - if !self.config.manual_propagation { - let message_id = (self.config.message_id_fn)(&msg); - self.forward_msg(msg, propagation_source); - debug!("Completed message handling for message: {:?}", message_id); - } - } - - /// Handles received subscriptions. - fn handle_received_subscriptions(&mut self, subscriptions: &[GossipsubSubscription], propagation_source: &PeerId) { - debug!( - "Handling subscriptions: {:?}, from source: {:?}", - subscriptions, propagation_source - ); - let subscribed_topics = match self.peer_topics.get_mut(propagation_source) { - Some(topics) => topics, - None => { - error!("Subscription by unknown peer: {:?}", &propagation_source); - return; - }, - }; - - for subscription in subscriptions { - // get the peers from the mapping, or insert empty lists if topic doesn't exist - let peer_list = self - .topic_peers - .entry(subscription.topic_hash.clone()) - .or_insert_with(Vec::new); - - match subscription.action { - GossipsubSubscriptionAction::Subscribe => { - if !peer_list.contains(propagation_source) { - debug!( - "SUBSCRIPTION: topic_peer: Adding gossip peer: {:?} to topic: {:?}", - propagation_source, subscription.topic_hash - ); - peer_list.push(*propagation_source); - } - - // add to the peer_topics mapping - if !subscribed_topics.contains(&subscription.topic_hash) { - info!( - "SUBSCRIPTION: Adding peer: {:?} to topic: {:?}", - propagation_source, subscription.topic_hash - ); - subscribed_topics.push(subscription.topic_hash.clone()); - } - - // if the mesh needs peers add the peer to the mesh - if let Some(peers) = self.mesh.get_mut(&subscription.topic_hash) { - if peers.len() < self.config.mesh_n_low { - debug!("SUBSCRIPTION: Adding peer {:?} to the mesh", propagation_source,); - } - peers.push(*propagation_source); - } - // generates a subscription event to be polled - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(GossipsubEvent::Subscribed { - peer_id: *propagation_source, - topic: subscription.topic_hash.clone(), - })); - }, - GossipsubSubscriptionAction::Unsubscribe => { - if let Some(pos) = peer_list.iter().position(|p| p == propagation_source) { - info!( - "SUBSCRIPTION: Removing gossip peer: {:?} from topic: {:?}", - propagation_source, subscription.topic_hash - ); - peer_list.remove(pos); - } - // remove topic from the peer_topics mapping - if let Some(pos) = subscribed_topics.iter().position(|t| t == &subscription.topic_hash) { - subscribed_topics.remove(pos); - } - // remove the peer from the mesh if it exists - if let Some(peers) = self.mesh.get_mut(&subscription.topic_hash) { - peers.retain(|peer| peer != propagation_source); - } - - // generate an unsubscribe event to be polled - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(GossipsubEvent::Unsubscribed { - peer_id: *propagation_source, - topic: subscription.topic_hash.clone(), - })); - }, - } - } - trace!("Completed handling subscriptions from source: {:?}", propagation_source); - } - - /// Heartbeat function which shifts the memcache and updates the mesh. - fn heartbeat(&mut self) { - debug!("Starting heartbeat"); - - let mut to_graft = HashMap::new(); - let mut to_prune = HashMap::new(); - - // maintain the mesh for each topic - for (topic_hash, peers) in self.mesh.iter_mut() { - // too little peers - add some - if peers.len() < self.config.mesh_n_low { - debug!( - "HEARTBEAT: Mesh low. Topic: {:?} Contains: {:?} needs: {:?}", - topic_hash.clone().into_string(), - peers.len(), - self.config.mesh_n_low - ); - // not enough peers - get mesh_n - current_length more - let desired_peers = self.config.mesh_n - peers.len(); - let peer_list = Self::get_random_peers(&self.topic_peers, topic_hash, desired_peers, { - |peer| !peers.contains(peer) - }); - for peer in &peer_list { - let current_topic = to_graft.entry(*peer).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - } - // update the mesh - debug!("Updating mesh, new mesh: {:?}", peer_list); - peers.extend(peer_list); - } - - // too many peers - remove some - if peers.len() > self.config.mesh_n_high { - debug!( - "HEARTBEAT: Mesh high. Topic: {:?} Contains: {:?} needs: {:?}", - topic_hash, - peers.len(), - self.config.mesh_n_high - ); - let excess_peer_no = peers.len() - self.config.mesh_n; - // shuffle the peers - let mut rng = rand::thread_rng(); - peers.shuffle(&mut rng); - // remove the first excess_peer_no peers adding them to to_prune - for _ in 0..excess_peer_no { - let peer = peers.pop().expect("There should always be enough peers to remove"); - let current_topic = to_prune.entry(peer).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - } - } - } - - // remove expired fanout topics - { - let fanout = &mut self.fanout; // help the borrow checker - let fanout_ttl = self.config.fanout_ttl; - self.fanout_last_pub.retain(|topic_hash, last_pub_time| { - if *last_pub_time + fanout_ttl < Instant::now() { - debug!( - "HEARTBEAT: Fanout topic removed due to timeout. Topic: {:?}", - topic_hash - ); - fanout.remove(topic_hash); - return false; - } - true - }); - } - - // maintain fanout - // check if our peers are still a part of the topic - for (topic_hash, peers) in self.fanout.iter_mut() { - let mut to_remove_peers = Vec::new(); - for peer in peers.iter() { - // is the peer still subscribed to the topic? - match self.peer_topics.get(peer) { - Some(topics) => { - if !topics.contains(topic_hash) { - debug!("HEARTBEAT: Peer removed from fanout for topic: {:?}", topic_hash); - to_remove_peers.push(*peer); - } - }, - None => { - // remove if the peer has disconnected - to_remove_peers.push(*peer); - }, - } - } - peers.retain(|peer| !to_remove_peers.contains(peer)); - - // not enough peers - if peers.len() < self.config.mesh_n { - debug!( - "HEARTBEAT: Fanout low. Contains: {:?} needs: {:?}", - peers.len(), - self.config.mesh_n - ); - let needed_peers = self.config.mesh_n - peers.len(); - let new_peers = Self::get_random_peers(&self.topic_peers, topic_hash, needed_peers, |peer| { - !peers.contains(peer) - }); - peers.extend(new_peers); - } - } - - self.emit_gossip(); - - // send graft/prunes - if !to_graft.is_empty() | !to_prune.is_empty() { - self.send_graft_prune(to_graft, to_prune); - } - - // piggyback pooled control messages - self.flush_control_pool(); - - // shift the memcache - self.mcache.shift(); - debug!("Completed Heartbeat"); - } - - /// Emits gossip - Send IHAVE messages to a random set of gossip peers. This is applied to mesh - /// and fanout peers - fn emit_gossip(&mut self) { - debug!("Started gossip"); - for (topic_hash, peers) in self.mesh.iter().chain(self.fanout.iter()) { - let message_ids = self.mcache.get_gossip_ids(topic_hash); - if message_ids.is_empty() { - return; - } - - // get gossip_lazy random peers - let to_msg_peers = Self::get_random_peers(&self.topic_peers, topic_hash, self.config.gossip_lazy, |peer| { - !peers.contains(peer) - }); - for peer in to_msg_peers { - // send an IHAVE message - Self::control_pool_add(&mut self.control_pool, peer, GossipsubControlAction::IHave { - topic_hash: topic_hash.clone(), - message_ids: message_ids.clone(), - }); - } - } - debug!("Completed gossip"); - } - - /// Handles multiple GRAFT/PRUNE messages and coalesces them into chunked gossip control - /// messages. - fn send_graft_prune( - &mut self, - to_graft: HashMap>, - mut to_prune: HashMap>, - ) { - // handle the grafts and overlapping prunes - for (peer, topics) in to_graft.iter() { - let mut grafts: Vec = topics - .iter() - .map(|topic_hash| GossipsubControlAction::Graft { - topic_hash: topic_hash.clone(), - }) - .collect(); - let mut prunes: Vec = to_prune - .remove(peer) - .unwrap_or_default() - .iter() - .map(|topic_hash| GossipsubControlAction::Prune { - topic_hash: topic_hash.clone(), - }) - .collect(); - grafts.append(&mut prunes); - - // send the control messages - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: grafts, - }); - self.notify_primary(*peer, event); - } - - // handle the remaining prunes - for (peer, topics) in to_prune.iter() { - let remaining_prunes = topics - .iter() - .map(|topic_hash| GossipsubControlAction::Prune { - topic_hash: topic_hash.clone(), - }) - .collect(); - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: remaining_prunes, - }); - self.notify_primary(*peer, event); - } - } - - /// Helper function which forwards a message to mesh\[topic\] peers. - fn forward_msg(&mut self, message: GossipsubMessage, source: &PeerId) { - let msg_id = (self.config.message_id_fn)(&message); - debug!("Forwarding message: {:?}", msg_id); - let mut recipient_peers = HashSet::new(); - - if self.config.i_am_relay { - // relay simply forwards the message to topic peers that included the relay to their relays mesh - for topic in &message.topics { - if let Some(topic_peers) = self.topic_peers.get(topic) { - for peer_id in topic_peers { - if peer_id != source - && peer_id != &message.source - && self.included_to_relays_mesh.contains(peer_id) - { - recipient_peers.insert(*peer_id); - } - } - } - } - } else { - // add mesh peers if the node is not relay - for topic in &message.topics { - if let Some(mesh_peers) = self.mesh.get(topic) { - for peer_id in mesh_peers { - if peer_id != source && peer_id != &message.source { - recipient_peers.insert(*peer_id); - } - } - } - } - } - - // forward the message to peers - if !recipient_peers.is_empty() { - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: vec![message.clone()], - control_msgs: Vec::new(), - }); - - for peer in recipient_peers.iter() { - if let Some(received_from_peers) = self.received.get(&msg_id) { - if received_from_peers.contains(peer) { - continue; - } - } - - debug!("Sending message: {:?} to peer {:?}", msg_id, peer); - self.notify_primary(*peer, event.clone()); - } - } - - if !self.relays_mesh.is_empty() { - debug!("Forwarding message to relays: {:?}", msg_id); - let message_source = message.source; - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: vec![message], - control_msgs: Vec::new(), - }); - - let relays: Vec<_> = self.relays_mesh.keys().copied().collect(); - for relay in relays { - if let Some(received_from_peers) = self.received.get(&msg_id) { - if received_from_peers.contains(&relay) { - continue; - } - } - - if relay != *source && relay != message_source { - debug!("Sending message: {:?} to relay {:?}", msg_id, relay); - self.notify_primary(relay, event.clone()); - } - } - debug!("Completed forwarding message to relays"); - } - debug!("Completed forwarding message"); - } - - /// Helper function to get a set of `n` random gossipsub peers for a `topic_hash` - /// filtered by the function `f`. - fn get_random_peers( - topic_peers: &HashMap>, - topic_hash: &TopicHash, - n: usize, - mut f: impl FnMut(&PeerId) -> bool, - ) -> Vec { - let mut gossip_peers = match topic_peers.get(topic_hash) { - // if they exist, filter the peers by `f` - Some(peer_list) => peer_list.iter().cloned().filter(|p| f(p)).collect(), - None => Vec::new(), - }; - - // if we have less than needed, return them - if gossip_peers.len() <= n { - debug!("RANDOM PEERS: Got {:?} peers", gossip_peers.len()); - return gossip_peers.to_vec(); - } - - // we have more peers than needed, shuffle them and return n of them - let mut rng = rand::thread_rng(); - gossip_peers.partial_shuffle(&mut rng, n); - - debug!("RANDOM PEERS: Got {:?} peers", n); - - gossip_peers[..n].to_vec() - } - - /// The helper function to get a set of `n` random peers from the `relays` hashset - /// filtered by the function `f`. - fn get_random_relays(relays: &HashSet, n: usize, mut f: impl FnMut(&PeerId) -> bool) -> Vec { - let mut relays: Vec<_> = relays.iter().cloned().filter(|p| f(p)).collect(); - - // if we have less than needed, return them - if relays.len() <= n { - debug!("RANDOM RELAYS: Got {:?} peers", relays.len()); - return relays; - } - - // we have more peers than needed, shuffle them and return n of them - let mut rng = rand::thread_rng(); - relays.partial_shuffle(&mut rng, n); - debug!("RANDOM RELAYS: Got {:?} peers", n); - - relays[..n].to_vec() - } - - // adds a control action to control_pool - fn control_pool_add( - control_pool: &mut HashMap>, - peer: PeerId, - control: GossipsubControlAction, - ) { - control_pool.entry(peer).or_insert_with(Vec::new).push(control); - } - - /// Produces a `TopicHash` for a topic given the gossipsub configuration. - fn topic_hash(&self, topic: Topic) -> TopicHash { - if self.config.hash_topics { - topic.sha256_hash() - } else { - topic.no_hash() - } - } - - /// Takes each control action mapping and turns it into a message - fn flush_control_pool(&mut self) { - let control_pool: Vec<_> = self.control_pool.drain().collect(); - for (peer, controls) in control_pool { - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: controls, - }); - self.notify_primary(peer, event); - } - } - - pub fn get_mesh_peers(&self, topic: &TopicHash) -> Vec { self.mesh.get(topic).cloned().unwrap_or_default() } - - pub fn get_topic_peers(&self, topic: &TopicHash) -> Vec { - self.topic_peers.get(topic).cloned().unwrap_or_default() - } - - pub fn get_num_peers(&self) -> usize { self.peer_topics.len() } - - pub fn get_peers_connections(&self) -> HashMap> { - self.peer_connections.clone() - } - - pub fn get_mesh(&self) -> &HashMap> { &self.mesh } - - pub fn get_relay_mesh(&self) -> Vec { self.relays_mesh.keys().cloned().collect() } - - pub fn relay_mesh_len(&self) -> usize { self.relays_mesh.len() } - - pub fn get_all_topic_peers(&self) -> &HashMap> { &self.topic_peers } - - pub fn get_all_peer_topics(&self) -> &HashMap> { &self.peer_topics } - - /// Get count of received messages in the [`GossipsubConfig::duplicate_cache_time`] period. - pub fn get_received_messages_in_period(&self) -> (Duration, usize) { (self.received.ttl(), self.received.len()) } - - pub fn get_config(&self) -> &GossipsubConfig { &self.config } - - /// Adds peers to relays mesh and notifies them they are added - fn add_peers_to_relays_mesh(&mut self, peers: Vec) { - for peer in &peers { - // other mesh size is unknown at this point - self.relays_mesh.insert(*peer, 0); - } - for peer in peers { - self.notify_included_to_relay_mesh(peer); - } - } - - #[allow(dead_code)] - fn remove_peer_from_relay_mesh(&mut self, peer: &PeerId) { - if self.relays_mesh.remove(peer).is_some() { - self.notify_excluded_from_relay_mesh(*peer) - } - } - - /// Cleans up relays mesh so it remains mesh_n peers - fn clean_up_relays_mesh(&mut self) { - let mesh_n = self.config.mesh_n; - let mut removed = Vec::with_capacity(self.relays_mesh.len() - mesh_n); - let explicit_relay_list = self.explicit_relay_list.clone(); - // perform 2 filter iterations to not keep excessive number of explicit peers in mesh - self.relays_mesh = self - .relays_mesh - .drain() - .enumerate() - .filter_map(|(i, peer)| { - if i < mesh_n || explicit_relay_list.contains(&peer.0) { - Some(peer) - } else { - removed.push(peer); - None - } - }) - .collect(); - - self.relays_mesh = self - .relays_mesh - .drain() - .enumerate() - .filter_map(|(i, peer)| { - if i < mesh_n { - Some(peer) - } else { - removed.push(peer); - None - } - }) - .collect(); - - for (peer, _) in removed { - self.notify_excluded_from_relay_mesh(peer) - } - } - - fn notify_included_to_relay_mesh(&mut self, peer: PeerId) { - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: vec![GossipsubControlAction::IncludedToRelaysMesh { - included: true, - mesh_size: self.relay_mesh_len(), - }], - }); - self.notify_primary(peer, event); - } - - fn notify_excluded_from_relay_mesh(&mut self, peer: PeerId) { - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: vec![GossipsubControlAction::IncludedToRelaysMesh { - included: false, - mesh_size: self.relay_mesh_len(), - }], - }); - self.notify_primary(peer, event); - } - - /// Notify the primary connection (the first connected point) of the peer. - /// Since `NotifyHandler::All` has been removed, the original `libp2p_gossipsub` notifies the connected peers using their primary connections. - /// See an example: https://github.com/libp2p/rust-libp2p/blob/v0.38.0/protocols/gossipsub/src/behaviour.rs#L3013 - fn notify_primary(&mut self, peer_id: PeerId, event: Arc) { - if let Some(points) = self.peer_connections.get(&peer_id) { - if !points.is_empty() { - let conn_id = points[0].0; - return self.notify_one(peer_id, conn_id, event); - } - } - warn!("Expected at least one connection of the peer '{}'", peer_id); - self.notify_any(peer_id, event); - } - - #[allow(dead_code)] - fn notify_all(&mut self, peer_id: PeerId, event: Arc) { - match self.peer_connections.get(&peer_id) { - Some(connected_points) => { - let connections: Vec<_> = connected_points.iter().map(|(conn_id, _point)| *conn_id).collect(); - for conn_id in connections { - self.notify_one(peer_id, conn_id, event.clone()); - } - }, - None => { - warn!( - "An attempt to notify a peer '{:?}' that is not in 'Gossipsub::peer_connections'", - peer_id - ) - }, - } - } - - fn notify_any(&mut self, peer_id: PeerId, event: Arc) { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::Any, - event, - }); - } - - fn notify_one(&mut self, peer_id: PeerId, conn_id: ConnectionId, event: Arc) { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::One(conn_id), - event, - }); - } - - fn maintain_relays_mesh(&mut self) { - if self.relays_mesh.len() < self.config.mesh_n_low { - info!( - "HEARTBEAT: relays low. Contains: {:?} needs: {:?}", - self.relays_mesh.len(), - self.config.mesh_n_low, - ); - // add peers 1 by 1 to avoid overloading peaks when node connects to several other nodes at once - let required = 1; - // get `n` relays that are not in the `relays_mesh` - let to_add = - Self::get_random_relays(&self.connected_relays, required, |p| !self.relays_mesh.contains_key(p)); - self.add_peers_to_relays_mesh(to_add); - } - - if self.relays_mesh.len() > self.config.mesh_n_high { - info!( - "HEARTBEAT: relays high. Contains: {:?} needs: {:?}", - self.relays_mesh.len(), - self.config.mesh_n, - ); - self.clean_up_relays_mesh(); - } - - let size = self.relays_mesh.len(); - for relay in self.relays_mesh.keys() { - Self::control_pool_add(&mut self.control_pool, *relay, GossipsubControlAction::MeshSize(size)); - } - } - - pub fn is_relay(&self) -> bool { self.config.i_am_relay } - - pub fn connected_relays(&self) -> Vec { self.connected_relays.iter().cloned().collect() } - - pub fn connected_relays_len(&self) -> usize { self.connected_relays.len() } - - pub fn is_connected_to_addr(&self, addr: &Multiaddr) -> bool { self.connected_addresses.contains(addr) } -} - -impl NetworkBehaviour for Gossipsub { - type ConnectionHandler = GossipsubHandler; - type OutEvent = GossipsubEvent; - - fn new_handler(&mut self) -> Self::ConnectionHandler { - GossipsubHandler::new(self.config.protocol_id.clone(), self.config.max_transmit_size) - } - - fn addresses_of_peer(&mut self, _: &PeerId) -> Vec { Vec::new() } - - fn inject_connection_established( - &mut self, - id: &PeerId, - conn_id: &ConnectionId, - point: &ConnectedPoint, - _: Option<&Vec>, - other_established: usize, - ) { - self.peer_connections - .entry(*id) - .or_insert_with(Default::default) - .push((*conn_id, point.clone())); - self.connected_addresses.push(point.get_remote_address().clone()); - - if other_established > 0 { - // For other actions, we only care about the first time a peer connects. - return; - } - - info!("New peer connected: {:?}", id); - // We need to send our subscriptions to the newly-connected node if we are not relay. - // Notify peer that we act as relay otherwise - if self.config.i_am_relay { - debug!("Sending IAmRelay to peer {:?}", id); - let event = Arc::new(GossipsubRpc { - messages: Vec::new(), - subscriptions: Vec::new(), - control_msgs: vec![GossipsubControlAction::IAmRelay(true)], - }); - self.notify_primary(*id, event); - } else { - let mut subscriptions = vec![]; - for topic_hash in self.mesh.keys() { - subscriptions.push(GossipsubSubscription { - topic_hash: topic_hash.clone(), - action: GossipsubSubscriptionAction::Subscribe, - }); - } - - if !subscriptions.is_empty() { - // send our subscriptions to the peer - let event = Arc::new(GossipsubRpc { - messages: Vec::new(), - subscriptions, - control_msgs: Vec::new(), - }); - self.notify_primary(*id, event); - } - } - // For the time being assume all gossipsub peers - self.peer_topics.insert(*id, Vec::new()); - } - - fn inject_connection_closed( - &mut self, - peer_id: &PeerId, - disconnected_conn_id: &ConnectionId, - disconnected_point: &ConnectedPoint, - _: ::Handler, - remaining_established: usize, - ) { - if let Entry::Occupied(mut o) = self.peer_connections.entry(*peer_id) { - let connected_points = o.get_mut(); - connected_points.retain(|(conn_id, _point)| conn_id != disconnected_conn_id); - if connected_points.is_empty() { - o.remove_entry(); - } - } - - self.connected_addresses - .retain(|addr| addr != disconnected_point.get_remote_address()); - - if remaining_established > 0 { - return; - } - - // remove from mesh, topic_peers, peer_topic and fanout - debug!("Peer disconnected: {:?}", peer_id); - { - let topics = match self.peer_topics.get(peer_id) { - Some(topics) => topics, - None => { - warn!("Disconnected node, not in connected nodes"); - return; - }, - }; - - // remove peer from all mappings - for topic in topics { - // check the mesh for the topic - if let Some(mesh_peers) = self.mesh.get_mut(topic) { - // check if the peer is in the mesh and remove it - if let Some(pos) = mesh_peers.iter().position(|p| p == peer_id) { - mesh_peers.remove(pos); - } - } - - // remove from topic_peers - if let Some(peer_list) = self.topic_peers.get_mut(topic) { - if let Some(pos) = peer_list.iter().position(|p| p == peer_id) { - peer_list.remove(pos); - } - // debugging purposes - else { - warn!("Disconnected node: {:?} not in topic_peers peer list", peer_id); - } - } else { - warn!( - "Disconnected node: {:?} with topic: {:?} not in topic_peers", - &peer_id, &topic - ); - } - - // remove from fanout - if let Some(peers) = self.fanout.get_mut(topic) { - peers.retain(|p| p != peer_id) - } - } - } - - self.relays_mesh.remove(peer_id); - self.connected_relays.remove(peer_id); - self.included_to_relays_mesh.remove(peer_id); - self.peer_connections.remove(peer_id); - // remove peer from peer_topics - let was_in = self.peer_topics.remove(peer_id); - debug_assert!(was_in.is_some()); - } - - fn inject_event(&mut self, propagation_source: PeerId, _: ConnectionId, event: GossipsubRpc) { - // Handle subscriptions - // Update connected peers topics - debug!("Event injected {:?}, source {:?}", event, propagation_source); - self.handle_received_subscriptions(&event.subscriptions, &propagation_source); - - // Handle messages - for message in event.messages { - self.handle_received_message(message, &propagation_source); - } - - // Handle control messages - // group some control messages, this minimises SendEvents (code is simplified to handle each event at a time however) - let mut ihave_msgs = vec![]; - let mut graft_msgs = vec![]; - let mut prune_msgs = vec![]; - for control_msg in event.control_msgs { - match control_msg { - GossipsubControlAction::IHave { - topic_hash, - message_ids, - } => { - ihave_msgs.push((topic_hash, message_ids)); - }, - GossipsubControlAction::IWant { message_ids } => self.handle_iwant(&propagation_source, message_ids), - GossipsubControlAction::Graft { topic_hash } => graft_msgs.push(topic_hash), - GossipsubControlAction::Prune { topic_hash } => prune_msgs.push(topic_hash), - GossipsubControlAction::IAmRelay(is_relay) => self.handle_i_am_relay(&propagation_source, is_relay), - GossipsubControlAction::IncludedToRelaysMesh { included, mesh_size } => { - self.handle_included_to_relays_mesh(&propagation_source, included, mesh_size) - }, - GossipsubControlAction::MeshSize(size) => { - if let Some(old_size) = self.relays_mesh.get_mut(&propagation_source) { - *old_size = size; - } - }, - } - } - if !ihave_msgs.is_empty() { - self.handle_ihave(&propagation_source, ihave_msgs); - } - if !graft_msgs.is_empty() { - self.handle_graft(&propagation_source, graft_msgs); - } - if !prune_msgs.is_empty() { - self.handle_prune(&propagation_source, prune_msgs); - } - } - - fn poll( - &mut self, - cx: &mut Context, - _: &mut impl PollParameters, - ) -> Poll> { - if let Some(event) = self.events.pop_front() { - // clone send event reference if others references are present - match event { - NetworkBehaviourAction::NotifyHandler { - peer_id, - handler, - event: send_event, - } => match Arc::try_unwrap(send_event) { - Ok(event) => { - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id, - event, - handler, - }); - }, - Err(event) => { - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id, - event: (*event).clone(), - handler, - }); - }, - }, - NetworkBehaviourAction::GenerateEvent(e) => { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(e)); - }, - NetworkBehaviourAction::Dial { opts, handler } => { - return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }); - }, - NetworkBehaviourAction::ReportObservedAddr { address, score } => { - return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { address, score }); - }, - NetworkBehaviourAction::CloseConnection { peer_id, connection } => { - return Poll::Ready(NetworkBehaviourAction::CloseConnection { peer_id, connection }); - }, - } - } - - while let Poll::Ready(Some(())) = self.heartbeat.poll_next_unpin(cx) { - self.heartbeat(); - } - - while let Poll::Ready(Some(())) = self.relay_mesh_maintenance_interval.poll_next_unpin(cx) { - self.maintain_relays_mesh(); - } - - Poll::Pending - } -} - -/// An RPC received/sent. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct GossipsubRpc { - /// List of messages that were part of this RPC query. - pub messages: Vec, - /// List of subscriptions. - pub subscriptions: Vec, - /// List of Gossipsub control messages. - pub control_msgs: Vec, -} - -/// Event that can happen on the gossipsub behaviour. -#[derive(Debug)] -pub enum GossipsubEvent { - /// A message has been received. This contains the PeerId that we received the message from, - /// the message id (used if the application layer needs to propagate the message) and the - /// message itself. - Message(PeerId, MessageId, GossipsubMessage), - - /// A remote subscribed to a topic. - Subscribed { - /// Remote that has subscribed. - peer_id: PeerId, - /// The topic it has subscribed to. - topic: TopicHash, - }, - - /// A remote unsubscribed from a topic. - Unsubscribed { - /// Remote that has unsubscribed. - peer_id: PeerId, - /// The topic it has subscribed from. - topic: TopicHash, - }, -} diff --git a/mm2src/gossipsub/src/behaviour/tests.rs b/mm2src/gossipsub/src/behaviour/tests.rs deleted file mode 100644 index 1c1923df0b..0000000000 --- a/mm2src/gossipsub/src/behaviour/tests.rs +++ /dev/null @@ -1,900 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -// collection of tests for the gossipsub network behaviour - -#[cfg(test)] -#[allow(clippy::module_inception)] -mod tests { - use super::super::*; - use crate::GossipsubConfigBuilder; - use libp2p_core::Endpoint; - use std::net::IpAddr; - use std::str::FromStr; - - // helper functions for testing - - // This function generates `peer_no` random PeerId's, subscribes to `topics` and subscribes the - // injected nodes to all topics if `to_subscribe` is set. All nodes are considered gossipsub nodes. - fn build_and_inject_nodes( - peer_no: usize, - topics: Vec, - gs_config: GossipsubConfig, - to_subscribe: bool, - ) -> (Gossipsub, Vec, Vec) { - // create a gossipsub struct - let mut gs: Gossipsub = Gossipsub::new(PeerId::random(), gs_config); - - let mut topic_hashes = vec![]; - - // subscribe to the topics - for t in topics { - let topic = Topic::new(t); - gs.subscribe(topic.clone()); - topic_hashes.push(topic.no_hash().clone()); - } - - // build and connect peer_no random peers - let mut peers = vec![]; - - for i in 0..peer_no { - let peer = PeerId::random(); - peers.push(peer); - ::inject_connection_established( - &mut gs, - &peer, - &ConnectionId::new(i), - &ConnectedPoint::Dialer { - address: Multiaddr::from(IpAddr::from_str("127.0.0.1").unwrap()), - role_override: Endpoint::Dialer, - }, - None, - 0, - ); - if to_subscribe { - gs.handle_received_subscriptions( - &topic_hashes - .iter() - .cloned() - .map(|t| GossipsubSubscription { - action: GossipsubSubscriptionAction::Subscribe, - topic_hash: t, - }) - .collect::>(), - &peer, - ); - }; - } - - (gs, peers, topic_hashes) - } - - #[test] - /// Test local node subscribing to a topic - fn test_subscribe() { - // The node should: - // - Create an empty vector in mesh[topic] - // - Send subscription request to all peers - // - run JOIN(topic) - - let subscribe_topic = vec![String::from("test_subscribe")]; - let (gs, _, topic_hashes) = build_and_inject_nodes(20, subscribe_topic, GossipsubConfig::default(), true); - - assert!( - gs.mesh.get(&topic_hashes[0]).is_some(), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - - // collect all the subscriptions - let subscriptions = gs.events.iter().fold(vec![], |mut collected_subscriptions, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for s in &event.subscriptions { - if s.action == GossipsubSubscriptionAction::Subscribe { - collected_subscriptions.push(s.clone()) - } - } - collected_subscriptions - }, - _ => collected_subscriptions, - }); - - // we sent a subscribe to all known peers - assert!( - subscriptions.len() == 20, - "Should send a subscription to all known peers" - ); - } - - #[test] - /// Test unsubscribe. - fn test_unsubscribe() { - // Unsubscribe should: - // - Remove the mesh entry for topic - // - Send UNSUBSCRIBE to all known peers - // - Call Leave - - let topic_strings = vec![String::from("topic1"), String::from("topic2")]; - let topics = topic_strings - .iter() - .map(|t| Topic::new(t.clone())) - .collect::>(); - - // subscribe to topic_strings - let (mut gs, _, topic_hashes) = build_and_inject_nodes(20, topic_strings, GossipsubConfig::default(), true); - - for topic_hash in &topic_hashes { - assert!( - gs.topic_peers.get(topic_hash).is_some(), - "Topic_peers contain a topic entry" - ); - assert!(gs.mesh.get(topic_hash).is_some(), "mesh should contain a topic entry"); - } - - // unsubscribe from both topics - assert!( - gs.unsubscribe(topics[0].clone()), - "should be able to unsubscribe successfully from each topic", - ); - assert!( - gs.unsubscribe(topics[1].clone()), - "should be able to unsubscribe successfully from each topic", - ); - - let subscriptions = gs.events.iter().fold(vec![], |mut collected_subscriptions, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for s in &event.subscriptions { - if s.action == GossipsubSubscriptionAction::Unsubscribe { - collected_subscriptions.push(s.clone()) - } - } - collected_subscriptions - }, - _ => collected_subscriptions, - }); - - // we sent a unsubscribe to all known peers, for two topics - assert!( - subscriptions.len() == 40, - "Should send an unsubscribe event to all known peers" - ); - - // check we clean up internal structures - for topic_hash in &topic_hashes { - assert!( - gs.mesh.get(topic_hash).is_none(), - "All topics should have been removed from the mesh" - ); - } - } - - #[test] - /// Test JOIN(topic) functionality. - fn test_join() { - // The Join function should: - // - Remove peers from fanout[topic] - // - Add any fanout[topic] peers to the mesh (up to mesh_n) - // - Fill up to mesh_n peers from known gossipsub peers in the topic - // - Send GRAFT messages to all nodes added to the mesh - - // This test is not an isolated unit test, rather it uses higher level, - // subscribe/unsubscribe to perform the test. - - let topic_strings = vec![String::from("topic1"), String::from("topic2")]; - let topics = topic_strings - .iter() - .map(|t| Topic::new(t.clone())) - .collect::>(); - - let (mut gs, _, topic_hashes) = build_and_inject_nodes(20, topic_strings, GossipsubConfig::default(), true); - - // unsubscribe, then call join to invoke functionality - assert!( - gs.unsubscribe(topics[0].clone()), - "should be able to unsubscribe successfully" - ); - assert!( - gs.unsubscribe(topics[1].clone()), - "should be able to unsubscribe successfully" - ); - - // re-subscribe - there should be peers associated with the topic - assert!( - gs.subscribe(topics[0].clone()), - "should be able to subscribe successfully" - ); - - // should have added mesh_n nodes to the mesh - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().len() == 6, - "Should have added 6 nodes to the mesh" - ); - - // there should be mesh_n GRAFT messages. - let graft_messages = gs - .control_pool - .iter() - .fold(vec![], |mut collected_grafts, (_, controls)| { - for c in controls.iter() { - if let GossipsubControlAction::Graft { topic_hash: _ } = c { - collected_grafts.push(c.clone()) - } - } - collected_grafts - }); - - assert_eq!( - graft_messages.len(), - 6, - "There should be 6 grafts messages sent to peers" - ); - - // verify fanout nodes - // add 3 random peers to the fanout[topic1] - gs.fanout.insert(topic_hashes[1].clone(), vec![]); - let new_peers = vec![]; - for _ in 0..3 { - let fanout_peers = gs.fanout.get_mut(&topic_hashes[1]).unwrap(); - fanout_peers.push(PeerId::random()); - } - - // subscribe to topic1 - gs.subscribe(topics[1].clone()); - - // the three new peers should have been added, along with 3 more from the pool. - assert!( - gs.mesh.get(&topic_hashes[1]).unwrap().len() == 6, - "Should have added 6 nodes to the mesh" - ); - let mesh_peers = gs.mesh.get(&topic_hashes[1]).unwrap(); - for new_peer in new_peers { - assert!( - mesh_peers.contains(new_peer), - "Fanout peer should be included in the mesh" - ); - } - - // there should now be 12 graft messages to be sent - let graft_messages = gs - .control_pool - .iter() - .fold(vec![], |mut collected_grafts, (_, controls)| { - for c in controls.iter() { - if let GossipsubControlAction::Graft { topic_hash: _ } = c { - collected_grafts.push(c.clone()) - } - } - collected_grafts - }); - - assert!( - graft_messages.len() == 12, - "There should be 12 grafts messages sent to peers" - ); - } - - /// Test local node publish to subscribed topic - #[test] - fn test_publish() { - // node should: - // - Send publish message to all peers - // - Insert message into gs.mcache and gs.received - - let publish_topic = String::from("test_publish"); - let (mut gs, _, topic_hashes) = - build_and_inject_nodes(20, vec![publish_topic.clone()], GossipsubConfig::default(), true); - - assert!( - gs.mesh.get(&topic_hashes[0]).is_some(), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - - // publish on topic - let publish_data = vec![0; 42]; - gs.publish(&Topic::new(publish_topic), publish_data); - - // Collect all publish messages - let publishes = gs.events.iter().fold(vec![], |mut collected_publish, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for s in &event.messages { - collected_publish.push(s.clone()); - } - collected_publish - }, - _ => collected_publish, - }); - - let msg_id = (gs.config.message_id_fn)(publishes.first().expect("Should contain > 0 entries")); - - assert!( - publishes.len() == 20, - "Should send a publish message to all known peers" - ); - - assert!( - gs.mcache.get(&msg_id).is_some(), - "Message cache should contain published message" - ); - assert!( - gs.received.get(&msg_id).is_some(), - "Received cache should contain published message" - ); - } - - /// Test local node publish to unsubscribed topic - #[test] - fn test_fanout() { - // node should: - // - Populate fanout peers - // - Send publish message to fanout peers - // - Insert message into gs.mcache and gs.received - let fanout_topic = String::from("test_fanout"); - let (mut gs, _, topic_hashes) = - build_and_inject_nodes(20, vec![fanout_topic.clone()], GossipsubConfig::default(), true); - - assert!( - gs.mesh.get(&topic_hashes[0]).is_some(), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - // Unsubscribe from topic - assert!( - gs.unsubscribe(Topic::new(fanout_topic.clone())), - "should be able to unsubscribe successfully from topic" - ); - - // Publish on unsubscribed topic - let publish_data = vec![0; 42]; - gs.publish(&Topic::new(fanout_topic.clone()), publish_data); - - assert_eq!( - gs.fanout.get(&TopicHash::from_raw(fanout_topic)).unwrap().len(), - gs.config.mesh_n, - "Fanout should contain `mesh_n` peers for fanout topic" - ); - - // Collect all publish messages - let publishes = gs.events.iter().fold(vec![], |mut collected_publish, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for s in &event.messages { - collected_publish.push(s.clone()); - } - collected_publish - }, - _ => collected_publish, - }); - - let msg_id = (gs.config.message_id_fn)(publishes.first().expect("Should contain > 0 entries")); - - assert_eq!( - publishes.len(), - gs.config.mesh_n, - "Should send a publish message to `mesh_n` fanout peers" - ); - - assert!( - gs.mcache.get(&msg_id).is_some(), - "Message cache should contain published message" - ); - assert!( - gs.received.get(&msg_id).is_some(), - "Received cache should contain published message" - ); - } - - #[test] - /// Test the gossipsub NetworkBehaviour peer connection logic. - fn test_inject_connected() { - let (gs, peers, topic_hashes) = build_and_inject_nodes( - 20, - vec![String::from("topic1"), String::from("topic2")], - GossipsubConfig::default(), - true, - ); - - // check that our subscriptions are sent to each of the peers - // collect all the SendEvents - let send_events: Vec<&NetworkBehaviourAction>> = gs - .events - .iter() - .filter(|e| matches!(e, NetworkBehaviourAction::NotifyHandler { .. })) - .collect(); - - // check that there are two subscriptions sent to each peer - for sevent in send_events.clone() { - if let NetworkBehaviourAction::NotifyHandler { event, .. } = sevent { - assert!( - event.subscriptions.len() == 2, - "There should be two subscriptions sent to each peer (1 for each topic)." - ); - }; - } - - // check that there are 20 send events created - assert!( - send_events.len() == 20, - "There should be a subscription event sent to each peer." - ); - - // should add the new peers to `peer_topics` with an empty vec as a gossipsub node - for peer in peers { - let known_topics = gs.peer_topics.get(&peer).unwrap(); - assert!( - known_topics == &topic_hashes, - "The topics for each node should all topics" - ); - } - } - - #[test] - /// Test subscription handling - fn test_handle_received_subscriptions() { - // For every subscription: - // SUBSCRIBE: - Add subscribed topic to peer_topics for peer. - // - Add peer to topics_peer. - // UNSUBSCRIBE - Remove topic from peer_topics for peer. - // - Remove peer from topic_peers. - - let topics = vec!["topic1", "topic2", "topic3", "topic4"] - .iter() - .map(|&t| String::from(t)) - .collect(); - let (mut gs, peers, topic_hashes) = build_and_inject_nodes(20, topics, GossipsubConfig::default(), false); - - // The first peer sends 3 subscriptions and 1 unsubscription - let mut subscriptions = topic_hashes[..3] - .iter() - .map(|topic_hash| GossipsubSubscription { - action: GossipsubSubscriptionAction::Subscribe, - topic_hash: topic_hash.clone(), - }) - .collect::>(); - - subscriptions.push(GossipsubSubscription { - action: GossipsubSubscriptionAction::Unsubscribe, - topic_hash: topic_hashes[topic_hashes.len() - 1].clone(), - }); - - let unknown_peer = PeerId::random(); - // process the subscriptions - // first and second peers send subscriptions - gs.handle_received_subscriptions(&subscriptions, &peers[0]); - gs.handle_received_subscriptions(&subscriptions, &peers[1]); - // unknown peer sends the same subscriptions - gs.handle_received_subscriptions(&subscriptions, &unknown_peer); - - // verify the result - - let peer_topics = gs.peer_topics.get(&peers[0]).unwrap().clone(); - assert!( - peer_topics == topic_hashes[..3].to_vec(), - "First peer should be subscribed to three topics" - ); - let peer_topics = gs.peer_topics.get(&peers[1]).unwrap().clone(); - assert!( - peer_topics == topic_hashes[..3].to_vec(), - "Second peer should be subscribed to three topics" - ); - - assert!( - gs.peer_topics.get(&unknown_peer).is_none(), - "Unknown peer should not have been added" - ); - - for topic_hash in topic_hashes[..3].iter() { - let topic_peers = gs.topic_peers.get(topic_hash).unwrap().clone(); - assert!( - topic_peers == peers[..2].to_vec(), - "Two peers should be added to the first three topics" - ); - } - - // Peer 0 unsubscribes from the first topic - - gs.handle_received_subscriptions( - &[GossipsubSubscription { - action: GossipsubSubscriptionAction::Unsubscribe, - topic_hash: topic_hashes[0].clone(), - }], - &peers[0], - ); - - let peer_topics = gs.peer_topics.get(&peers[0]).unwrap().clone(); - assert!( - peer_topics == topic_hashes[1..3].to_vec(), - "Peer should be subscribed to two topics" - ); - - let topic_peers = gs.topic_peers.get(&topic_hashes[0]).unwrap().clone(); // only gossipsub at the moment - assert!( - topic_peers == peers[1..2].to_vec(), - "Only the second peers should be in the first topic" - ); - } - - #[test] - /// Test Gossipsub.get_random_peers() function - fn test_get_random_peers() { - // generate a default GossipsubConfig - let gs_config = GossipsubConfig::default(); - // create a gossipsub struct - let mut gs: Gossipsub = Gossipsub::new(PeerId::random(), gs_config); - - // create a topic and fill it with some peers - let topic_hash = Topic::new("Test".into()).no_hash(); - let mut peers = vec![]; - for _ in 0..20 { - peers.push(PeerId::random()) - } - - gs.topic_peers.insert(topic_hash.clone(), peers.clone()); - - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 5, |_| true); - assert!(random_peers.len() == 5, "Expected 5 peers to be returned"); - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 30, |_| true); - assert!(random_peers.len() == 20, "Expected 20 peers to be returned"); - assert!(random_peers == peers, "Expected no shuffling"); - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 20, |_| true); - assert!(random_peers.len() == 20, "Expected 20 peers to be returned"); - assert!(random_peers == peers, "Expected no shuffling"); - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 0, |_| true); - assert!(random_peers.is_empty(), "Expected 0 peers to be returned"); - // test the filter - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 5, |_| false); - assert!(random_peers.is_empty(), "Expected 0 peers to be returned"); - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 10, |peer| peers.contains(peer)); - assert!(random_peers.len() == 10, "Expected 10 peers to be returned"); - } - - /// Tests that the correct message is sent when a peer asks for a message in our cache. - #[test] - fn test_handle_iwant_msg_cached() { - let (mut gs, peers, _) = build_and_inject_nodes(20, Vec::new(), GossipsubConfig::default(), true); - - let id = gs.config.message_id_fn; - - let message = GossipsubMessage { - source: peers[11], - data: vec![1, 2, 3, 4], - sequence_number: 1u64, - topics: Vec::new(), - }; - let msg_id = id(&message); - gs.mcache.put(message); - - gs.handle_iwant(&peers[7], vec![msg_id.clone()]); - - // the messages we are sending - let sent_messages = gs.events.iter().fold(vec![], |mut collected_messages, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for c in &event.messages { - collected_messages.push(c.clone()) - } - collected_messages - }, - _ => collected_messages, - }); - - assert!( - sent_messages.iter().any(|msg| id(msg) == msg_id), - "Expected the cached message to be sent to an IWANT peer" - ); - } - - /// Tests that messages are sent correctly depending on the shifting of the message cache. - #[test] - fn test_handle_iwant_msg_cached_shifted() { - let (mut gs, peers, _) = build_and_inject_nodes(20, Vec::new(), GossipsubConfig::default(), true); - - let id = gs.config.message_id_fn; - // perform 10 memshifts and check that it leaves the cache - for shift in 1..10 { - let message = GossipsubMessage { - source: peers[11], - data: vec![1, 2, 3, 4], - sequence_number: shift, - topics: Vec::new(), - }; - let msg_id = id(&message); - gs.mcache.put(message.clone()); - for _ in 0..shift { - gs.mcache.shift(); - } - - gs.handle_iwant(&peers[7], vec![msg_id.clone()]); - - // is the message is being sent? - let message_exists = gs.events.iter().any(|e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - event.messages.iter().any(|msg| id(msg) == msg_id) - }, - _ => false, - }); - // default history_length is 5, expect no messages after shift > 5 - if shift < 5 { - assert!( - message_exists, - "Expected the cached message to be sent to an IWANT peer before 5 shifts" - ); - } else { - assert!( - !message_exists, - "Expected the cached message to not be sent to an IWANT peer after 5 shifts" - ); - } - } - } - - #[test] - // tests that an event is not created when a peers asks for a message not in our cache - fn test_handle_iwant_msg_not_cached() { - let (mut gs, peers, _) = build_and_inject_nodes(20, Vec::new(), GossipsubConfig::default(), true); - - let events_before = gs.events.len(); - gs.handle_iwant(&peers[7], vec![MessageId(String::from("unknown id"))]); - let events_after = gs.events.len(); - - assert_eq!(events_before, events_after, "Expected event count to stay the same"); - } - - #[test] - // tests that an event is created when a peer shares that it has a message we want - fn test_handle_ihave_subscribed_and_msg_not_cached() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - gs.handle_ihave(&peers[7], vec![(topic_hashes[0].clone(), vec![MessageId( - String::from("unknown id"), - )])]); - - // check that we sent an IWANT request for `unknown id` - let iwant_exists = match gs.control_pool.get(&peers[7]) { - Some(controls) => controls.iter().any(|c| match c { - GossipsubControlAction::IWant { message_ids } => - { - #[allow(clippy::cmp_owned)] - message_ids.iter().any(|m| *m.0 == String::from("unknown id")) - }, - _ => false, - }), - _ => false, - }; - - assert!( - iwant_exists, - "Expected to send an IWANT control message for unkown message id" - ); - } - - #[test] - // tests that an event is not created when a peer shares that it has a message that - // we already have - fn test_handle_ihave_subscribed_and_msg_cached() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - let msg_id = MessageId(String::from("known id")); - gs.received.insert(msg_id.clone(), SmallVec::new()); - - let events_before = gs.events.len(); - gs.handle_ihave(&peers[7], vec![(topic_hashes[0].clone(), vec![msg_id])]); - let events_after = gs.events.len(); - - assert_eq!(events_before, events_after, "Expected event count to stay the same") - } - - #[test] - // test that an event is not created when a peer shares that it has a message in - // a topic that we are not subscribed to - fn test_handle_ihave_not_subscribed() { - let (mut gs, peers, _) = build_and_inject_nodes(20, vec![], GossipsubConfig::default(), true); - - let events_before = gs.events.len(); - gs.handle_ihave(&peers[7], vec![( - TopicHash::from_raw(String::from("unsubscribed topic")), - vec![MessageId(String::from("irrelevant id"))], - )]); - let events_after = gs.events.len(); - - assert_eq!(events_before, events_after, "Expected event count to stay the same") - } - - #[test] - // tests that a peer is added to our mesh when we are both subscribed - // to the same topic - fn test_handle_graft_is_subscribed() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - gs.handle_graft(&peers[7], topic_hashes.clone()); - - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to have been added to mesh" - ); - } - - #[test] - // tests that a peer is not added to our mesh when they are subscribed to - // a topic that we are not - fn test_handle_graft_is_not_subscribed() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - gs.handle_graft(&peers[7], vec![TopicHash::from_raw(String::from("unsubscribed topic"))]); - - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to have been added to mesh" - ); - } - - #[test] - // tests multiple topics in a single graft message - fn test_handle_graft_multiple_topics() { - let topics: Vec = vec!["topic1", "topic2", "topic3", "topic4"] - .iter() - .map(|&t| String::from(t)) - .collect(); - - let (mut gs, peers, topic_hashes) = build_and_inject_nodes(20, topics, GossipsubConfig::default(), true); - - let mut their_topics = topic_hashes.clone(); - // their_topics = [topic1, topic2, topic3] - // our_topics = [topic1, topic2, topic4] - their_topics.pop(); - gs.leave(&their_topics[2]); - - gs.handle_graft(&peers[7], their_topics.clone()); - - for item in topic_hashes.iter().take(2) { - assert!( - gs.mesh.get(item).unwrap().contains(&peers[7]), - "Expected peer to be in the mesh for the first 2 topics" - ); - } - - assert!( - gs.mesh.get(&topic_hashes[2]).is_none(), - "Expected the second topic to not be in the mesh" - ); - } - - #[test] - // tests that a peer is removed from our mesh - fn test_handle_prune_peer_in_mesh() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - // insert peer into our mesh for 'topic1' - gs.mesh.insert(topic_hashes[0].clone(), peers.clone()); - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to be in mesh" - ); - - gs.handle_prune(&peers[7], topic_hashes.clone()); - assert!( - !gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to be removed from mesh" - ); - } - - #[test] - fn test_maintain_relays_mesh_n_high() { - let peer_no = 20; - let (mut gs, peers, _) = build_and_inject_nodes(peer_no, vec![], GossipsubConfig::default(), false); - for peer in peers { - gs.relays_mesh.insert(peer, 0); - } - assert_eq!(peer_no, gs.relays_mesh.len(), "relays mesh must contain 20 peers"); - gs.maintain_relays_mesh(); - assert_eq!( - gs.config.mesh_n, - gs.relays_mesh.len(), - "relays mesh must contain mesh_n peers after maintenance" - ); - assert_eq!(gs.events.len(), 14); - for event in gs.events { - match event { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - assert_eq!(event.control_msgs, vec![GossipsubControlAction::IncludedToRelaysMesh { - included: false, - mesh_size: gs.relays_mesh.len(), - }]); - }, - _ => panic!("Invalid NetworkBehaviourAction variant"), - } - } - } - - #[test] - fn test_maintain_relays_mesh_n_low() { - let peer_no = 20; - let (mut gs, peers, _) = build_and_inject_nodes(peer_no, vec![], GossipsubConfig::default(), false); - for (i, peer) in peers.into_iter().enumerate() { - if i < 3 { - gs.relays_mesh.insert(peer, 0); - } else { - gs.connected_relays.insert(peer); - } - } - assert_eq!(3, gs.relays_mesh.len(), "relays mesh must contain 3 peers"); - gs.maintain_relays_mesh(); - assert_eq!( - 4, - gs.relays_mesh.len(), - "relays mesh must contain 1 more peer after maintenance (4 total)" - ); - assert_eq!(gs.events.len(), 1); - for event in gs.events { - match event { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - assert_eq!(event.control_msgs, vec![GossipsubControlAction::IncludedToRelaysMesh { - included: true, - mesh_size: gs.relays_mesh.len(), - }]); - }, - _ => panic!("Invalid NetworkBehaviourAction variant"), - } - } - } - - #[test] - fn test_process_included_to_relays_mesh() { - let peer_no = 2; - let config = GossipsubConfigBuilder::default().i_am_relay(true).build(); - let (mut gs, peers, _) = build_and_inject_nodes(peer_no, vec![], config, false); - for peer in &peers { - gs.connected_relays.insert(*peer); - } - - gs.handle_included_to_relays_mesh(&peers[0], true, 1); - assert!(gs.relays_mesh.contains_key(&peers[0])); - - gs.handle_included_to_relays_mesh(&peers[0], false, 1); - assert!(!gs.relays_mesh.contains_key(&peers[0])); - } - - #[test] - fn test_process_included_to_relays_mesh_n_high_exceeded() { - let peer_no = 14; - let config = GossipsubConfigBuilder::default().i_am_relay(true).build(); - let (mut gs, peers, _) = build_and_inject_nodes(peer_no, vec![], config, false); - for (i, peer) in peers.iter().enumerate() { - gs.connected_relays.insert(*peer); - if i < 13 { - gs.relays_mesh.insert(*peer, 0); - } - } - - gs.handle_included_to_relays_mesh(&peers[13], true, 1); - assert!(!gs.relays_mesh.contains_key(&peers[13])); - - match gs.events.pop_back().unwrap() { - NetworkBehaviourAction::NotifyHandler { event, peer_id, .. } => { - assert_eq!(event.control_msgs, vec![GossipsubControlAction::IncludedToRelaysMesh { - included: false, - mesh_size: gs.relay_mesh_len(), - }]); - assert_eq!(peer_id, peers[13]); - }, - _ => panic!("Invalid NetworkBehaviourAction variant"), - } - } -} diff --git a/mm2src/gossipsub/src/config.rs b/mm2src/gossipsub/src/config.rs deleted file mode 100644 index f74343e21d..0000000000 --- a/mm2src/gossipsub/src/config.rs +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -use crate::protocol::{GossipsubMessage, MessageId}; -use std::borrow::Cow; -use std::time::Duration; - -/// If the `no_source_id` flag is set, the IDENTITY_SOURCE value is used as the source of the -/// packet. -pub const IDENTITY_SOURCE: [u8; 3] = [0, 1, 0]; - -/// Configuration parameters that define the performance of the gossipsub network. -#[derive(Clone)] -pub struct GossipsubConfig { - /// The protocol id to negotiate this protocol (default is `/meshsub/1.0.0`). - pub protocol_id: Cow<'static, [u8]>, - - // Overlay network parameters. - /// Number of heartbeats to keep in the `memcache` (default is 5). - pub history_length: usize, - - /// Number of past heartbeats to gossip about (default is 3). - pub history_gossip: usize, - - /// Target number of peers for the mesh network (D in the spec, default is 6). - pub mesh_n: usize, - - /// Minimum number of peers in mesh network before adding more (D_lo in the spec, default is 4). - pub mesh_n_low: usize, - - /// Maximum number of peers in mesh network before removing some (D_high in the spec, default - /// is 12). - pub mesh_n_high: usize, - - /// Number of peers to emit gossip to during a heartbeat (D_lazy in the spec, default is 6). - pub gossip_lazy: usize, - - /// Initial delay in each heartbeat (default is 5 seconds). - pub heartbeat_initial_delay: Duration, - - /// Time between each heartbeat (default is 1 second). - pub heartbeat_interval: Duration, - - /// Time to live for fanout peers (default is 60 seconds). - pub fanout_ttl: Duration, - - /// The maximum byte size for each gossip (default is 2048 bytes). - pub max_transmit_size: usize, - - /// Duplicates are prevented by storing message id's of known messages in an LRU time cache. - /// This settings sets the time period that messages are stored in the cache. Duplicates can be - /// received if duplicate messages are sent at a time greater than this setting apart. The - /// default is 1 minute. - pub duplicate_cache_time: Duration, - - /// Flag determining if gossipsub topics are hashed or sent as plain strings (default is false). - pub hash_topics: bool, - - /// When set, all published messages will have a 0 source `PeerId` (default is false). - pub no_source_id: bool, - - /// When set to `true`, prevents automatic forwarding of all received messages. This setting - /// allows a user to validate the messages before propagating them to their peers. If set to - /// true, the user must manually call `propagate_message()` on the behaviour to forward message - /// once validated (default is false). - pub manual_propagation: bool, - - /// A user-defined function allowing the user to specify the message id of a gossipsub message. - /// The default value is to concatenate the source peer id with a sequence number. Setting this - /// parameter allows the user to address packets arbitrarily. One example is content based - /// addressing, where this function may be set to `hash(message)`. This would prevent messages - /// of the same content from being duplicated. - /// - /// The function takes a `GossipsubMessage` as input and outputs a String to be interpreted as - /// the message id. - pub message_id_fn: fn(&GossipsubMessage) -> MessageId, - - pub i_am_relay: bool, -} - -impl Default for GossipsubConfig { - fn default() -> GossipsubConfig { - GossipsubConfig { - protocol_id: Cow::Borrowed(b"/meshsub/1.0.0"), - history_length: 5, - history_gossip: 3, - mesh_n: 6, - mesh_n_low: 4, - mesh_n_high: 12, - gossip_lazy: 6, // default to mesh_n - heartbeat_initial_delay: Duration::from_secs(5), - heartbeat_interval: Duration::from_secs(1), - fanout_ttl: Duration::from_secs(60), - max_transmit_size: 2048, - duplicate_cache_time: Duration::from_secs(60), - hash_topics: false, // default compatibility with floodsub - no_source_id: false, - manual_propagation: false, - message_id_fn: |message| { - // default message id is: source + sequence number - let mut source_string = message.source.to_base58(); - source_string.push_str(&message.sequence_number.to_string()); - MessageId(source_string) - }, - i_am_relay: false, - } - } -} - -#[derive(Default)] -pub struct GossipsubConfigBuilder { - config: GossipsubConfig, -} - -impl GossipsubConfigBuilder { - // set default values - pub fn new() -> GossipsubConfigBuilder { GossipsubConfigBuilder::default() } - - pub fn protocol_id(&mut self, protocol_id: impl Into>) -> &mut Self { - self.config.protocol_id = protocol_id.into(); - self - } - - pub fn history_length(&mut self, history_length: usize) -> &mut Self { - assert!( - history_length >= self.config.history_gossip, - "The history_length must be greater than or equal to the history_gossip length" - ); - self.config.history_length = history_length; - self - } - - pub fn history_gossip(&mut self, history_gossip: usize) -> &mut Self { - assert!( - self.config.history_length >= history_gossip, - "The history_length must be greater than or equal to the history_gossip length" - ); - self.config.history_gossip = history_gossip; - self - } - - pub fn mesh_n(&mut self, mesh_n: usize) -> &mut Self { - assert!( - self.config.mesh_n_low <= mesh_n && mesh_n <= self.config.mesh_n_high, - "The following equality doesn't hold mesh_n_low <= mesh_n <= mesh_n_high" - ); - self.config.mesh_n = mesh_n; - self - } - - pub fn mesh_n_low(&mut self, mesh_n_low: usize) -> &mut Self { - assert!( - mesh_n_low <= self.config.mesh_n && self.config.mesh_n <= self.config.mesh_n_high, - "The following equality doesn't hold mesh_n_low <= mesh_n <= mesh_n_high" - ); - self.config.mesh_n_low = mesh_n_low; - self - } - - pub fn mesh_n_high(&mut self, mesh_n_high: usize) -> &mut Self { - assert!( - self.config.mesh_n_low <= self.config.mesh_n && self.config.mesh_n <= mesh_n_high, - "The following equality doesn't hold mesh_n_low <= mesh_n <= mesh_n_high" - ); - self.config.mesh_n_high = mesh_n_high; - self - } - - pub fn gossip_lazy(&mut self, gossip_lazy: usize) -> &mut Self { - self.config.gossip_lazy = gossip_lazy; - self - } - - pub fn heartbeat_initial_delay(&mut self, heartbeat_initial_delay: Duration) -> &mut Self { - self.config.heartbeat_initial_delay = heartbeat_initial_delay; - self - } - pub fn heartbeat_interval(&mut self, heartbeat_interval: Duration) -> &mut Self { - self.config.heartbeat_interval = heartbeat_interval; - self - } - pub fn fanout_ttl(&mut self, fanout_ttl: Duration) -> &mut Self { - self.config.fanout_ttl = fanout_ttl; - self - } - pub fn max_transmit_size(&mut self, max_transmit_size: usize) -> &mut Self { - self.config.max_transmit_size = max_transmit_size; - self - } - - pub fn hash_topics(&mut self) -> &mut Self { - self.config.hash_topics = true; - self - } - - pub fn no_source_id(&mut self) -> &mut Self { - self.config.no_source_id = true; - self - } - - pub fn manual_propagation(&mut self) -> &mut Self { - self.config.manual_propagation = true; - self - } - - pub fn message_id_fn(&mut self, id_fn: fn(&GossipsubMessage) -> MessageId) -> &mut Self { - self.config.message_id_fn = id_fn; - self - } - - pub fn i_am_relay(&mut self, i_am_relay: bool) -> &mut Self { - self.config.i_am_relay = i_am_relay; - self - } - - pub fn build(&self) -> GossipsubConfig { self.config.clone() } -} - -impl std::fmt::Debug for GossipsubConfig { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let mut builder = f.debug_struct("GossipsubConfig"); - let _ = builder.field("protocol_id", &self.protocol_id); - let _ = builder.field("history_length", &self.history_length); - let _ = builder.field("history_gossip", &self.history_gossip); - let _ = builder.field("mesh_n", &self.mesh_n); - let _ = builder.field("mesh_n_low", &self.mesh_n_low); - let _ = builder.field("mesh_n_high", &self.mesh_n_high); - let _ = builder.field("gossip_lazy", &self.gossip_lazy); - let _ = builder.field("heartbeat_initial_delay", &self.heartbeat_initial_delay); - let _ = builder.field("heartbeat_interval", &self.heartbeat_interval); - let _ = builder.field("fanout_ttl", &self.fanout_ttl); - let _ = builder.field("max_transmit_size", &self.max_transmit_size); - let _ = builder.field("hash_topics", &self.hash_topics); - let _ = builder.field("no_source_id", &self.no_source_id); - let _ = builder.field("manual_propagation", &self.manual_propagation); - let _ = builder.field("i_am_relay", &self.i_am_relay); - builder.finish() - } -} diff --git a/mm2src/gossipsub/src/handler.rs b/mm2src/gossipsub/src/handler.rs deleted file mode 100644 index 2b71cf13dd..0000000000 --- a/mm2src/gossipsub/src/handler.rs +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -use crate::behaviour::GossipsubRpc; -use crate::protocol::{GossipsubCodec, ProtocolConfig}; -use futures::prelude::*; -use futures_codec::Framed; -use libp2p_core::upgrade::{InboundUpgrade, OutboundUpgrade}; -use libp2p_swarm::handler::{ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, KeepAlive, - SubstreamProtocol}; -use libp2p_swarm::NegotiatedSubstream; -use log::{debug, error, trace, warn}; -use smallvec::SmallVec; -use std::{borrow::Cow, - io, - pin::Pin, - task::{Context, Poll}}; - -/// Protocol Handler that manages a single long-lived substream with a peer. -pub struct GossipsubHandler { - /// Upgrade configuration for the gossipsub protocol. - listen_protocol: SubstreamProtocol, - - /// The single long-lived outbound substream. - outbound_substream: Option, - - /// The single long-lived inbound substream. - inbound_substream: Option, - - /// Queue of values that we want to send to the remote. - send_queue: SmallVec<[GossipsubRpc; 16]>, - - /// Flag indicating that an outbound substream is being established to prevent duplicate - /// requests. - outbound_substream_establishing: bool, - - /// Flag determining whether to maintain the connection to the peer. - keep_alive: KeepAlive, -} - -/// State of the inbound substream, opened either by us or by the remote. -#[allow(clippy::large_enum_variant)] -enum InboundSubstreamState { - /// Waiting for a message from the remote. The idle state for an inbound substream. - WaitingInput(Framed), - /// The substream is being closed. - Closing(Framed), - /// An error occurred during processing. - Poisoned, -} - -/// State of the outbound substream, opened either by us or by the remote. -#[allow(clippy::large_enum_variant)] -enum OutboundSubstreamState { - /// Waiting for the user to send a message. The idle state for an outbound substream. - WaitingOutput(Framed), - /// Waiting to send a message to the remote. - PendingSend(Framed, GossipsubRpc), - /// Waiting to flush the substream so that the data arrives to the remote. - PendingFlush(Framed), - /// The substream is being closed. Used by either substream. - _Closing(Framed), - /// An error occurred during processing. - Poisoned, -} - -impl GossipsubHandler { - /// Builds a new `GossipsubHandler`. - pub fn new(protocol_id: impl Into>, max_transmit_size: usize) -> Self { - GossipsubHandler { - listen_protocol: SubstreamProtocol::new(ProtocolConfig::new(protocol_id, max_transmit_size), ()), - inbound_substream: None, - outbound_substream: None, - send_queue: SmallVec::new(), - keep_alive: KeepAlive::Yes, - outbound_substream_establishing: false, - } - } -} - -impl Default for GossipsubHandler { - fn default() -> Self { - GossipsubHandler { - listen_protocol: SubstreamProtocol::new(ProtocolConfig::default(), ()), - inbound_substream: None, - outbound_substream: None, - send_queue: SmallVec::new(), - keep_alive: KeepAlive::Yes, - outbound_substream_establishing: false, - } - } -} - -impl ConnectionHandler for GossipsubHandler { - type InEvent = GossipsubRpc; - type OutEvent = GossipsubRpc; - type Error = io::Error; - type InboundProtocol = ProtocolConfig; - type OutboundProtocol = ProtocolConfig; - type OutboundOpenInfo = GossipsubRpc; - type InboundOpenInfo = (); - - fn listen_protocol(&self) -> SubstreamProtocol { - self.listen_protocol.clone() - } - - fn inject_fully_negotiated_inbound( - &mut self, - substream: >::Output, - _info: Self::InboundOpenInfo, - ) { - // new inbound substream. Replace the current one, if it exists. - trace!("New inbound substream request"); - self.inbound_substream = Some(InboundSubstreamState::WaitingInput(substream)); - } - - fn inject_fully_negotiated_outbound( - &mut self, - substream: >::Output, - message: Self::OutboundOpenInfo, - ) { - self.outbound_substream_establishing = false; - // Should never establish a new outbound substream if one already exists. - // If this happens, an outbound message is not sent. - if self.outbound_substream.is_some() { - warn!("Established an outbound substream with one already available"); - // Add the message back to the send queue - self.send_queue.push(message); - } else { - self.outbound_substream = Some(OutboundSubstreamState::PendingSend(substream, message)); - } - } - - fn inject_event(&mut self, message: GossipsubRpc) { self.send_queue.push(message); } - - fn inject_dial_upgrade_error( - &mut self, - _: Self::OutboundOpenInfo, - _: ConnectionHandlerUpgrErr<>::Error>, - ) { - self.outbound_substream_establishing = false; - // Ignore upgrade errors for now. - // If a peer doesn't support this protocol, this will just ignore them, but not disconnect - // them. - } - - fn connection_keep_alive(&self) -> KeepAlive { self.keep_alive } - - #[allow(clippy::type_complexity)] - fn poll( - &mut self, - cx: &mut Context, - ) -> Poll> { - // determine if we need to create the stream - if !self.send_queue.is_empty() && self.outbound_substream.is_none() && !self.outbound_substream_establishing { - let message = self.send_queue.remove(0); - self.send_queue.shrink_to_fit(); - self.outbound_substream_establishing = true; - return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { - protocol: self.listen_protocol.clone().map_info(|()| message), - }); - } - - loop { - match std::mem::replace(&mut self.inbound_substream, Some(InboundSubstreamState::Poisoned)) { - // inbound idle state - Some(InboundSubstreamState::WaitingInput(mut substream)) => { - match substream.poll_next_unpin(cx) { - Poll::Ready(Some(Ok(message))) => { - self.inbound_substream = Some(InboundSubstreamState::WaitingInput(substream)); - return Poll::Ready(ConnectionHandlerEvent::Custom(message)); - }, - Poll::Ready(Some(Err(e))) => { - debug!("Inbound substream error while awaiting input: {:?}", e); - self.inbound_substream = Some(InboundSubstreamState::Closing(substream)); - }, - // peer closed the stream - Poll::Ready(None) => { - self.inbound_substream = Some(InboundSubstreamState::Closing(substream)); - }, - Poll::Pending => { - self.inbound_substream = Some(InboundSubstreamState::WaitingInput(substream)); - break; - }, - } - }, - Some(InboundSubstreamState::Closing(mut substream)) => { - match Sink::poll_close(Pin::new(&mut substream), cx) { - Poll::Ready(res) => { - if let Err(e) = res { - // Don't close the connection but just drop the inbound substream. - // In case the remote has more to send, they will open up a new - // substream. - debug!("Inbound substream error while closing: {:?}", e); - } - - self.inbound_substream = None; - if self.outbound_substream.is_none() { - self.keep_alive = KeepAlive::No; - } - break; - }, - Poll::Pending => { - self.inbound_substream = Some(InboundSubstreamState::Closing(substream)); - break; - }, - } - }, - None => { - self.inbound_substream = None; - break; - }, - Some(InboundSubstreamState::Poisoned) => panic!("Error occurred during inbound stream processing"), - } - } - - loop { - match std::mem::replace(&mut self.outbound_substream, Some(OutboundSubstreamState::Poisoned)) { - // outbound idle state - Some(OutboundSubstreamState::WaitingOutput(substream)) => { - if !self.send_queue.is_empty() { - let message = self.send_queue.remove(0); - self.send_queue.shrink_to_fit(); - self.outbound_substream = Some(OutboundSubstreamState::PendingSend(substream, message)); - } else { - self.outbound_substream = Some(OutboundSubstreamState::WaitingOutput(substream)); - break; - } - }, - Some(OutboundSubstreamState::PendingSend(mut substream, message)) => { - match Sink::poll_ready(Pin::new(&mut substream), cx) { - Poll::Ready(Ok(())) => match Sink::start_send(Pin::new(&mut substream), message) { - Ok(()) => self.outbound_substream = Some(OutboundSubstreamState::PendingFlush(substream)), - Err(e) => { - if let io::ErrorKind::PermissionDenied = e.kind() { - error!("Message over the maximum transmission limit was not sent."); - self.outbound_substream = Some(OutboundSubstreamState::WaitingOutput(substream)); - } else { - return Poll::Ready(ConnectionHandlerEvent::Close(e)); - } - }, - }, - Poll::Ready(Err(e)) => { - debug!("Outbound substream error while sending output: {:?}", e); - return Poll::Ready(ConnectionHandlerEvent::Close(e)); - }, - Poll::Pending => { - self.outbound_substream = Some(OutboundSubstreamState::PendingSend(substream, message)); - break; - }, - } - }, - Some(OutboundSubstreamState::PendingFlush(mut substream)) => { - match Sink::poll_flush(Pin::new(&mut substream), cx) { - Poll::Ready(Ok(())) => { - self.outbound_substream = Some(OutboundSubstreamState::WaitingOutput(substream)) - }, - Poll::Ready(Err(e)) => return Poll::Ready(ConnectionHandlerEvent::Close(e)), - Poll::Pending => { - self.outbound_substream = Some(OutboundSubstreamState::PendingFlush(substream)); - break; - }, - } - }, - // Currently never used - manual shutdown may implement this in the future - Some(OutboundSubstreamState::_Closing(mut substream)) => { - match Sink::poll_close(Pin::new(&mut substream), cx) { - Poll::Ready(Ok(())) => { - self.outbound_substream = None; - if self.inbound_substream.is_none() { - self.keep_alive = KeepAlive::No; - } - break; - }, - Poll::Ready(Err(e)) => { - debug!("Outbound substream error while closing: {:?}", e); - return Poll::Ready(ConnectionHandlerEvent::Close(io::Error::new( - io::ErrorKind::BrokenPipe, - "Failed to close outbound substream", - ))); - }, - Poll::Pending => { - self.outbound_substream = Some(OutboundSubstreamState::_Closing(substream)); - break; - }, - } - }, - None => { - self.outbound_substream = None; - break; - }, - Some(OutboundSubstreamState::Poisoned) => panic!("Error occurred during outbound stream processing"), - } - } - - Poll::Pending - } -} diff --git a/mm2src/gossipsub/src/lib.rs b/mm2src/gossipsub/src/lib.rs deleted file mode 100644 index e0efa95571..0000000000 --- a/mm2src/gossipsub/src/lib.rs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -//! Gossipsub is a P2P pubsub (publish/subscription) routing layer designed to extend upon -//! flooodsub and meshsub routing protocols. -//! -//! # Overview -//! -//! *Note: The gossipsub protocol specifications -//! (https://github.com/libp2p/specs/tree/master/pubsub/gossipsub) provide an outline for the -//! routing protocol. They should be consulted for further detail.* -//! -//! Gossipsub is a blend of meshsub for data and randomsub for mesh metadata. It provides bounded -//! degree and amplification factor with the meshsub construction and augments it using gossip -//! propagation of metadata with the randomsub technique. -//! -//! The router maintains an overlay mesh network of peers on which to efficiently send messages and -//! metadata. Peers use control messages to broadcast and request known messages and -//! subscribe/unsubscribe from topics in the mesh network. -//! -//! # Important Discrepancies -//! -//! This section outlines the current implementation's potential discrepancies from that of other -//! implementations, due to undefined elements in the current specification. -//! -//! - **Topics** - In gossipsub, topics configurable by the `hash_topics` configuration parameter. -//! Topics are of type `TopicHash`. The current go implementation uses raw utf-8 strings, and this -//! is default configuration in rust-libp2p. Topics can be hashed (SHA256 hashed then base64 -//! encoded) by setting the `hash_topics` configuration parameter to true. -//! -//! - **Sequence Numbers** - A message on the gossipsub network is identified by the source -//! `PeerId` and a nonce (sequence number) of the message. The sequence numbers in this -//! implementation are sent as raw bytes across the wire. They are 64-bit big-endian unsigned -//! integers. They are chosen at random in this implementation of gossipsub, but are sequential in -//! the current go implementation. -//! -//! # Using Gossipsub -//! -//! ## GossipsubConfig -//! -//! The [`GossipsubConfig`] struct specifies various network performance/tuning configuration -//! parameters. Specifically it specifies: -//! -//! [`GossipsubConfig`]: struct.GossipsubConfig.html -//! -//! - `protocol_id` - The protocol id that this implementation will accept connections on. -//! - `history_length` - The number of heartbeats which past messages are kept in cache (default: 5). -//! - `history_gossip` - The number of past heartbeats that the node will send gossip metadata -//! about (default: 3). -//! - `mesh_n` - The target number of peers store in the local mesh network. -//! (default: 6). -//! - `mesh_n_low` - The minimum number of peers in the local mesh network before. -//! trying to add more peers to the mesh from the connected peer pool (default: 4). -//! - `mesh_n_high` - The maximum number of peers in the local mesh network before removing peers to -//! reach `mesh_n` peers (default: 12). -//! - `gossip_lazy` - The number of peers that the local node will gossip to during a heartbeat (default: `mesh_n` = 6). -//! - `heartbeat_initial_delay - The initial time delay before starting the first heartbeat (default: 5 seconds). -//! - `heartbeat_interval` - The time between each heartbeat (default: 1 second). -//! - `fanout_ttl` - The fanout time to live time period. The timeout required before removing peers from the fanout -//! for a given topic (default: 1 minute). -//! - `max_transmit_size` - This sets the maximum transmission size for total gossipsub messages on the network. -//! - `hash_topics` - Whether to hash the topics using base64(SHA256(topic)) or to leave as plain utf-8 strings. -//! - `manual_propagation` - Whether gossipsub should immediately forward received messages on the -//! network. For applications requiring message validation, this should be set to false, then the -//! application should call `propagate_message(message_id, propagation_source)` once validated, to -//! propagate the message to peers. -//! -//! This struct implements the `Default` trait and can be initialised via -//! `GossipsubConfig::default()`. -//! -//! -//! ## Gossipsub -//! -//! The [`Gossipsub`] struct implements the `NetworkBehaviour` trait allowing it to act as the -//! routing behaviour in a `Swarm`. This struct requires an instance of `PeerId` and -//! [`GossipsubConfig`]. -//! -//! [`Gossipsub`]: struct.Gossipsub.html - -//! ## Example -//! -//! An example of initialising a gossipsub compatible swarm: -//! -//! ```ignore -//! #extern crate libp2p; -//! #extern crate futures; -//! #extern crate tokio; -//! #use libp2p::gossipsub::GossipsubEvent; -//! #use libp2p::{gossipsub, secio, -//! # tokio_codec::{FramedRead, LinesCodec}, -//! #}; -//! let local_key = secio::SecioKeyPair::ed25519_generated().unwrap(); -//! let local_pub_key = local_key.to_public_key(); -//! -//! // Set up an encrypted TCP Transport over the Mplex and Yamux protocols -//! let transport = libp2p::build_development_transport(local_key); -//! -//! // Create a Floodsub/Gossipsub topic -//! let topic = libp2p::floodsub::TopicBuilder::new("example").build(); -//! -//! // Create a Swarm to manage peers and events -//! let mut swarm = { -//! // set default parameters for gossipsub -//! let gossipsub_config = gossipsub::GossipsubConfig::default(); -//! // build a gossipsub network behaviour -//! let mut gossipsub = -//! gossipsub::Gossipsub::new(local_pub_key.clone().into_peer_id(), gossipsub_config); -//! gossipsub.subscribe(topic.clone()); -//! libp2p::Swarm::new( -//! transport, -//! gossipsub, -//! libp2p::core::topology::MemoryTopology::empty(local_pub_key), -//! ) -//! }; -//! -//! // Listen on all interfaces and whatever port the OS assigns -//! let addr = libp2p::Swarm::listen_on(&mut swarm, "/ip4/0.0.0.0/tcp/0".parse().unwrap()).unwrap(); -//! println!("Listening on {:?}", addr); -//! ``` - -pub mod protocol; - -mod behaviour; -mod config; -mod handler; -mod mcache; -mod topic; - -mod rpc_proto { - include!(concat!(env!("OUT_DIR"), "/gossipsub.pb.rs")); -} - -pub use self::behaviour::{Gossipsub, GossipsubEvent, GossipsubRpc}; -pub use self::config::{GossipsubConfig, GossipsubConfigBuilder}; -pub use self::protocol::{GossipsubMessage, MessageId}; -pub use self::topic::{Topic, TopicHash}; diff --git a/mm2src/gossipsub/src/mcache.rs b/mm2src/gossipsub/src/mcache.rs deleted file mode 100644 index fe92cd3c93..0000000000 --- a/mm2src/gossipsub/src/mcache.rs +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -extern crate fnv; - -use crate::protocol::{GossipsubMessage, MessageId}; -use crate::topic::TopicHash; -use std::collections::HashMap; - -/// CacheEntry stored in the history. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct CacheEntry { - mid: MessageId, - topics: Vec, -} - -/// MessageCache struct holding history of messages. -#[derive(Clone)] -pub struct MessageCache { - msgs: HashMap, - history: Vec>, - gossip: usize, - msg_id: fn(&GossipsubMessage) -> MessageId, -} - -/// Implementation of the MessageCache. -impl MessageCache { - pub fn new(gossip: usize, history_capacity: usize, msg_id: fn(&GossipsubMessage) -> MessageId) -> MessageCache { - MessageCache { - gossip, - msgs: HashMap::default(), - history: vec![Vec::new(); history_capacity], - msg_id, - } - } - - /// Creates a `MessageCache` with a default message id function. - #[allow(dead_code)] - pub fn new_default(gossip: usize, history_capacity: usize) -> MessageCache { - let default_id = |message: &GossipsubMessage| { - // default message id is: source + sequence number - let mut source_string = message.source.to_base58(); - source_string.push_str(&message.sequence_number.to_string()); - MessageId(source_string) - }; - MessageCache { - gossip, - msgs: HashMap::default(), - history: vec![Vec::new(); history_capacity], - msg_id: default_id, - } - } - - /// Put a message into the memory cache - pub fn put(&mut self, msg: GossipsubMessage) { - let message_id = (self.msg_id)(&msg); - let cache_entry = CacheEntry { - mid: message_id.clone(), - topics: msg.topics.clone(), - }; - - self.msgs.insert(message_id, msg); - - self.history[0].push(cache_entry); - } - - /// Get a message with `message_id` - pub fn get(&self, message_id: &MessageId) -> Option<&GossipsubMessage> { self.msgs.get(message_id) } - - /// Get a list of GossipIds for a given topic - pub fn get_gossip_ids(&self, topic: &TopicHash) -> Vec { - self.history[..self.gossip] - .iter() - .fold(vec![], |mut current_entries, entries| { - // search for entries with desired topic - let mut found_entries: Vec = entries - .iter() - .filter_map(|entry| { - if entry.topics.iter().any(|t| t == topic) { - Some(entry.mid.clone()) - } else { - None - } - }) - .collect(); - - // generate the list - current_entries.append(&mut found_entries); - current_entries - }) - } - - /// Shift the history array down one and delete messages associated with the - /// last entry - pub fn shift(&mut self) { - for entry in self.history.pop().expect("history is always > 1") { - self.msgs.remove(&entry.mid); - } - - // Insert an empty vec in position 0 - self.history.insert(0, Vec::new()); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{Topic, TopicHash}; - use libp2p_core::PeerId; - - fn gen_testm(x: u64, topics: Vec) -> GossipsubMessage { - let u8x: u8 = x as u8; - let source = PeerId::random(); - let data: Vec = vec![u8x]; - let sequence_number = x; - - GossipsubMessage { - source, - data, - sequence_number, - topics, - } - } - - #[test] - /// Test that the message cache can be created. - fn test_new_cache() { - let default_id = |message: &GossipsubMessage| { - // default message id is: source + sequence number - let mut source_string = message.source.to_base58(); - source_string.push_str(&message.sequence_number.to_string()); - MessageId(source_string) - }; - let x: usize = 3; - let mc = MessageCache::new(x, 5, default_id); - - assert_eq!(mc.gossip, x); - } - - #[test] - /// Test you can put one message and get one. - fn test_put_get_one() { - let mut mc = MessageCache::new_default(10, 15); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - - let m = gen_testm(10, vec![topic1_hash, topic2_hash]); - - mc.put(m.clone()); - - assert!(mc.history[0].len() == 1); - - let fetched = mc.get(&(mc.msg_id)(&m)); - - assert!(fetched.is_some()); - - // Make sure it is the same fetched message - match fetched { - Some(x) => assert_eq!(*x, m), - _ => panic!("expected {:?}", m), - } - } - - #[test] - /// Test attempting to 'get' with a wrong id. - fn test_get_wrong() { - let mut mc = MessageCache::new_default(10, 15); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - - let m = gen_testm(10, vec![topic1_hash, topic2_hash]); - - mc.put(m); - - // Try to get an incorrect ID - let wrong_id = MessageId(String::from("wrongid")); - let fetched = mc.get(&wrong_id); - assert!(fetched.is_none()); - } - - #[test] - /// Test attempting to 'get' empty message cache. - fn test_get_empty() { - let mc = MessageCache::new_default(10, 15); - - // Try to get an incorrect ID - let wrong_string = MessageId(String::from("imempty")); - let fetched = mc.get(&wrong_string); - assert!(fetched.is_none()); - } - - #[test] - /// Test adding a message with no topics. - fn test_no_topic_put() { - let mut mc = MessageCache::new_default(3, 5); - - // Build the message - let m = gen_testm(1, vec![]); - mc.put(m.clone()); - - let fetched = mc.get(&(mc.msg_id)(&m)); - - // Make sure it is the same fetched message - match fetched { - Some(x) => assert_eq!(*x, m), - _ => panic!("expected {:?}", m), - } - } - - #[test] - /// Test shift mechanism. - fn test_shift() { - let mut mc = MessageCache::new_default(1, 5); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - - // Build the message - for i in 0..10 { - let m = gen_testm(i, vec![topic1_hash.clone(), topic2_hash.clone()]); - mc.put(m.clone()); - } - - mc.shift(); - - // Ensure the shift occurred - assert!(mc.history[0].is_empty()); - assert!(mc.history[1].len() == 10); - - // Make sure no messages deleted - assert!(mc.msgs.len() == 10); - } - - #[test] - /// Test Shift with no additions. - fn test_empty_shift() { - let mut mc = MessageCache::new_default(1, 5); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - // Build the message - for i in 0..10 { - let m = gen_testm(i, vec![topic1_hash.clone(), topic2_hash.clone()]); - mc.put(m.clone()); - } - - mc.shift(); - - // Ensure the shift occurred - assert!(mc.history[0].is_empty()); - assert!(mc.history[1].len() == 10); - - mc.shift(); - - assert!(mc.history[2].len() == 10); - assert!(mc.history[1].is_empty()); - assert!(mc.history[0].is_empty()); - } - - #[test] - /// Test shift to see if the last history messages are removed. - fn test_remove_last_from_shift() { - let mut mc = MessageCache::new_default(4, 5); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - // Build the message - for i in 0..10 { - let m = gen_testm(i, vec![topic1_hash.clone(), topic2_hash.clone()]); - mc.put(m.clone()); - } - - // Shift right until deleting messages - mc.shift(); - mc.shift(); - mc.shift(); - mc.shift(); - - assert_eq!(mc.history[mc.history.len() - 1].len(), 10); - - // Shift and delete the messages - mc.shift(); - assert_eq!(mc.history[mc.history.len() - 1].len(), 0); - assert_eq!(mc.history[0].len(), 0); - assert_eq!(mc.msgs.len(), 0); - } -} diff --git a/mm2src/gossipsub/src/protocol.rs b/mm2src/gossipsub/src/protocol.rs deleted file mode 100644 index 1173a89b22..0000000000 --- a/mm2src/gossipsub/src/protocol.rs +++ /dev/null @@ -1,397 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -use crate::behaviour::GossipsubRpc; -use crate::rpc_proto; -use crate::topic::TopicHash; -use byteorder::{BigEndian, ByteOrder}; -use bytes::Bytes; -use bytes::BytesMut; -use common::some_or_return_ok_none; -use futures::future; -use futures::prelude::*; -use futures_codec::{Decoder, Encoder, Framed}; -use libp2p_core::{InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo}; -use prost::Message as ProtobufMessage; -use std::{borrow::Cow, io, iter, pin::Pin}; -use unsigned_varint::codec; - -/// Implementation of the `ConnectionUpgrade` for the Gossipsub protocol. -#[derive(Debug, Clone)] -pub struct ProtocolConfig { - protocol_id: Cow<'static, [u8]>, - max_transmit_size: usize, -} - -impl Default for ProtocolConfig { - fn default() -> Self { - Self { - protocol_id: Cow::Borrowed(b"/meshsub/1.0.0"), - max_transmit_size: 2048, - } - } -} - -impl ProtocolConfig { - /// Builds a new `ProtocolConfig`. - /// Sets the maximum gossip transmission size. - pub fn new(protocol_id: impl Into>, max_transmit_size: usize) -> ProtocolConfig { - ProtocolConfig { - protocol_id: protocol_id.into(), - max_transmit_size, - } - } -} - -impl UpgradeInfo for ProtocolConfig { - type Info = Cow<'static, [u8]>; - type InfoIter = iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { iter::once(self.protocol_id.clone()) } -} - -type PinBoxFut = Pin> + Send>>; - -impl InboundUpgrade for ProtocolConfig -where - TSocket: AsyncRead + AsyncWrite + Unpin + Send + 'static, -{ - type Output = Framed; - type Error = io::Error; - type Future = PinBoxFut; - - fn upgrade_inbound(self, socket: TSocket, _: Self::Info) -> Self::Future { - let mut length_codec = codec::UviBytes::default(); - length_codec.set_max_len(self.max_transmit_size); - Box::pin(future::ok(Framed::new(socket, GossipsubCodec { length_codec }))) - } -} - -impl OutboundUpgrade for ProtocolConfig -where - TSocket: AsyncWrite + AsyncRead + Unpin + Send + 'static, -{ - type Output = Framed; - type Error = io::Error; - type Future = PinBoxFut; - - fn upgrade_outbound(self, socket: TSocket, _: Self::Info) -> Self::Future { - let mut length_codec = codec::UviBytes::default(); - length_codec.set_max_len(self.max_transmit_size); - Box::pin(future::ok(Framed::new(socket, GossipsubCodec { length_codec }))) - } -} - -/* Gossip codec for the framing */ - -pub struct GossipsubCodec { - /// Codec to encode/decode the Unsigned varint length prefix of the frames. - length_codec: codec::UviBytes, -} - -impl Encoder for GossipsubCodec { - type Item = GossipsubRpc; - type Error = io::Error; - - fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { - // messages - let publish = item - .messages - .into_iter() - .map(|message| rpc_proto::Message { - from: Some(message.source.to_bytes()), - data: Some(message.data), - seqno: Some(message.sequence_number.to_be_bytes().to_vec()), - topic_ids: message.topics.into_iter().map(TopicHash::into_string).collect(), - }) - .collect::>(); - - // subscriptions - let subscriptions = item - .subscriptions - .into_iter() - .map(|sub| rpc_proto::rpc::SubOpts { - subscribe: Some(sub.action == GossipsubSubscriptionAction::Subscribe), - topic_id: Some(sub.topic_hash.into_string()), - }) - .collect::>(); - - // control messages - let mut control = rpc_proto::ControlMessage { - ihave: Vec::new(), - iwant: Vec::new(), - graft: Vec::new(), - prune: Vec::new(), - iamrelay: None, - included_to_relays_mesh: None, - mesh_size: None, - }; - - let empty_control_msg = item.control_msgs.is_empty(); - - for action in item.control_msgs { - match action { - // collect all ihave messages - GossipsubControlAction::IHave { - topic_hash, - message_ids, - } => { - let rpc_ihave = rpc_proto::ControlIHave { - topic_id: Some(topic_hash.into_string()), - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }; - control.ihave.push(rpc_ihave); - }, - GossipsubControlAction::IWant { message_ids } => { - let rpc_iwant = rpc_proto::ControlIWant { - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }; - control.iwant.push(rpc_iwant); - }, - GossipsubControlAction::Graft { topic_hash } => { - let rpc_graft = rpc_proto::ControlGraft { - topic_id: Some(topic_hash.into_string()), - }; - control.graft.push(rpc_graft); - }, - GossipsubControlAction::Prune { topic_hash } => { - let rpc_prune = rpc_proto::ControlPrune { - topic_id: Some(topic_hash.into_string()), - }; - control.prune.push(rpc_prune); - }, - GossipsubControlAction::IAmRelay(is_relay) => { - control.iamrelay = Some(is_relay); - }, - GossipsubControlAction::IncludedToRelaysMesh { included, mesh_size } => { - control.included_to_relays_mesh = Some(rpc_proto::IncludedToRelaysMesh { - included, - mesh_size: mesh_size as u32, - }); - }, - GossipsubControlAction::MeshSize(size) => { - control.mesh_size = Some(size as u32); - }, - } - } - - let rpc = rpc_proto::Rpc { - subscriptions, - publish, - control: if empty_control_msg { None } else { Some(control) }, - }; - - let mut buf = Vec::with_capacity(rpc.encoded_len()); - - rpc.encode(&mut buf).expect("Buffer has sufficient capacity"); - - // length prefix the protobuf message, ensuring the max limit is not hit - self.length_codec.encode(Bytes::from(buf), dst) - } -} - -impl Decoder for GossipsubCodec { - type Item = GossipsubRpc; - type Error = io::Error; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - let packet = some_or_return_ok_none!(self.length_codec.decode(src)?); - - let rpc = rpc_proto::Rpc::decode(&packet[..])?; - - let mut messages = Vec::with_capacity(rpc.publish.len()); - for publish in rpc.publish.into_iter() { - // ensure the sequence number is a u64 - let seq_no = publish - .seqno - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "sequence number was not provided"))?; - if seq_no.len() != 8 { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "sequence number has an incorrect size", - )); - } - messages.push(GossipsubMessage { - source: PeerId::from_bytes(&publish.from.unwrap_or_default()) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid Peer Id"))?, - data: publish.data.unwrap_or_default(), - sequence_number: BigEndian::read_u64(&seq_no), - topics: publish.topic_ids.into_iter().map(TopicHash::from_raw).collect(), - }); - } - - let mut control_msgs = Vec::new(); - - if let Some(rpc_control) = rpc.control { - // Collect the gossipsub control messages - let ihave_msgs: Vec = rpc_control - .ihave - .into_iter() - .map(|ihave| GossipsubControlAction::IHave { - topic_hash: TopicHash::from_raw(ihave.topic_id.unwrap_or_default()), - message_ids: ihave.message_ids.into_iter().map(MessageId).collect::>(), - }) - .collect(); - - let iwant_msgs: Vec = rpc_control - .iwant - .into_iter() - .map(|iwant| GossipsubControlAction::IWant { - message_ids: iwant.message_ids.into_iter().map(MessageId).collect::>(), - }) - .collect(); - - let graft_msgs: Vec = rpc_control - .graft - .into_iter() - .map(|graft| GossipsubControlAction::Graft { - topic_hash: TopicHash::from_raw(graft.topic_id.unwrap_or_default()), - }) - .collect(); - - let prune_msgs: Vec = rpc_control - .prune - .into_iter() - .map(|prune| GossipsubControlAction::Prune { - topic_hash: TopicHash::from_raw(prune.topic_id.unwrap_or_default()), - }) - .collect(); - - control_msgs.extend(ihave_msgs); - control_msgs.extend(iwant_msgs); - control_msgs.extend(graft_msgs); - control_msgs.extend(prune_msgs); - - if let Some(is_relay) = rpc_control.iamrelay { - control_msgs.extend(iter::once(GossipsubControlAction::IAmRelay(is_relay))); - } - - if let Some(mesh_size) = rpc_control.mesh_size { - control_msgs.extend(iter::once(GossipsubControlAction::MeshSize(mesh_size as usize))); - } - - if let Some(msg) = rpc_control.included_to_relays_mesh { - control_msgs.extend(iter::once(GossipsubControlAction::IncludedToRelaysMesh { - included: msg.included, - mesh_size: msg.mesh_size as usize, - })); - } - } - - Ok(Some(GossipsubRpc { - messages, - subscriptions: rpc - .subscriptions - .into_iter() - .map(|sub| GossipsubSubscription { - action: if Some(true) == sub.subscribe { - GossipsubSubscriptionAction::Subscribe - } else { - GossipsubSubscriptionAction::Unsubscribe - }, - topic_hash: TopicHash::from_raw(sub.topic_id.unwrap_or_default()), - }) - .collect(), - control_msgs, - })) - } -} - -/// A type for gossipsub message ids. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct MessageId(pub String); - -impl std::fmt::Display for MessageId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } -} - -impl From for String { - fn from(mid: MessageId) -> Self { mid.0 } -} - -/// A message received by the gossipsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct GossipsubMessage { - /// Id of the peer that published this message. - pub source: PeerId, - - /// Content of the message. Its meaning is out of scope of this library. - pub data: Vec, - - /// A random sequence number. - pub sequence_number: u64, - - /// List of topics this message belongs to. - /// - /// Each message can belong to multiple topics at once. - pub topics: Vec, -} - -/// A subscription received by the gossipsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct GossipsubSubscription { - /// Action to perform. - pub action: GossipsubSubscriptionAction, - /// The topic from which to subscribe or unsubscribe. - pub topic_hash: TopicHash, -} - -/// Action that a subscription wants to perform. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum GossipsubSubscriptionAction { - /// The remote wants to subscribe to the given topic. - Subscribe, - /// The remote wants to unsubscribe from the given topic. - Unsubscribe, -} - -/// A Control message received by the gossipsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum GossipsubControlAction { - /// Node broadcasts known messages per topic - IHave control message. - IHave { - /// The topic of the messages. - topic_hash: TopicHash, - /// A list of known message ids (peer_id + sequence _number) as a string. - message_ids: Vec, - }, - /// The node requests specific message ids (peer_id + sequence _number) - IWant control message. - IWant { - /// A list of known message ids (peer_id + sequence _number) as a string. - message_ids: Vec, - }, - /// The node has been added to the mesh - Graft control message. - Graft { - /// The mesh topic the peer should be added to. - topic_hash: TopicHash, - }, - /// The node has been removed from the mesh - Prune control message. - Prune { - /// The mesh topic the peer should be removed from. - topic_hash: TopicHash, - }, - IAmRelay(bool), - /// Whether the node included or excluded from other node relays mesh - IncludedToRelaysMesh { - included: bool, - mesh_size: usize, - }, - MeshSize(usize), -} diff --git a/mm2src/gossipsub/src/rpc.proto b/mm2src/gossipsub/src/rpc.proto deleted file mode 100644 index 8a194011a9..0000000000 --- a/mm2src/gossipsub/src/rpc.proto +++ /dev/null @@ -1,87 +0,0 @@ -syntax = "proto2"; - -package gossipsub.pb; - -message RPC { - repeated SubOpts subscriptions = 1; - repeated Message publish = 2; - - message SubOpts { - optional bool subscribe = 1; // subscribe or unsubscribe - optional string topic_id = 2; - } - - optional ControlMessage control = 3; -} - -message Message { - optional bytes from = 1; - optional bytes data = 2; - optional bytes seqno = 3; - repeated string topic_ids = 4; -} - -message IncludedToRelaysMesh { - required bool included = 1; - required uint32 mesh_size = 2; -} - -message ControlMessage { - repeated ControlIHave ihave = 1; - repeated ControlIWant iwant = 2; - repeated ControlGraft graft = 3; - repeated ControlPrune prune = 4; - optional bool iamrelay = 5; - optional IncludedToRelaysMesh included_to_relays_mesh = 6; - optional uint32 mesh_size = 7; -} - -message ControlIHave { - optional string topic_id = 1; - repeated string message_ids = 2; -} - -message ControlIWant { - repeated string message_ids= 1; -} - -message ControlGraft { - optional string topic_id = 1; -} - -message ControlGraftRelay {} - -message ControlPrune { - optional string topic_id = 1; -} - -message ControlPruneRelay {} - -// topicID = hash(topicDescriptor); (not the topic.name) -message TopicDescriptor { - optional string name = 1; - optional AuthOpts auth = 2; - optional EncOpts enc = 3; - - message AuthOpts { - optional AuthMode mode = 1; - repeated bytes keys = 2; // root keys to trust - - enum AuthMode { - NONE = 0; // no authentication, anyone can publish - KEY = 1; // only messages signed by keys in the topic descriptor are accepted - WOT = 2; // web of trust, certificates can allow publisher set to grow - } - } - - message EncOpts { - optional EncMode mode = 1; - repeated bytes key_hashes = 2; // the hashes of the shared keys used (salted) - - enum EncMode { - NONE = 0; // no encryption, anyone can read - SHAREDKEY = 1; // messages are encrypted with shared key - WOT = 2; // web of trust, certificates can allow publisher set to grow - } - } -} diff --git a/mm2src/gossipsub/src/topic.rs b/mm2src/gossipsub/src/topic.rs deleted file mode 100644 index 970ea8947a..0000000000 --- a/mm2src/gossipsub/src/topic.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -use crate::rpc_proto; -use base64::encode; -use prost::Message; -use sha2::{Digest, Sha256}; -use std::fmt; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct TopicHash { - /// The topic hash. Stored as a string to align with the protobuf API. - hash: String, -} - -impl TopicHash { - pub fn from_raw(hash: impl Into) -> TopicHash { TopicHash { hash: hash.into() } } - - pub fn into_string(self) -> String { self.hash } - - pub fn as_str(&self) -> &str { &self.hash } -} - -/// A gossipsub topic. -#[derive(Debug, Clone)] -pub struct Topic { - topic: String, -} - -impl Topic { - pub fn new(topic: String) -> Self { Topic { topic } } - - /// Creates a `TopicHash` by SHA256 hashing the topic then base64 encoding the - /// hash. - pub fn sha256_hash(&self) -> TopicHash { - let topic_descripter = rpc_proto::TopicDescriptor { - name: Some(self.topic.clone()), - auth: None, - enc: None, - }; - let mut bytes = Vec::with_capacity(topic_descripter.encoded_len()); - topic_descripter.encode(&mut bytes).expect("buffer is large enough"); - let hash = encode(Sha256::digest(&bytes).as_slice()); - - TopicHash { hash } - } - - /// Creates a `TopicHash` as a raw string. - pub fn no_hash(&self) -> TopicHash { - TopicHash { - hash: self.topic.clone(), - } - } -} - -impl fmt::Display for Topic { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.topic) } -} - -impl fmt::Display for TopicHash { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.hash) } -} diff --git a/mm2src/gossipsub/tests/smoke.rs b/mm2src/gossipsub/tests/smoke.rs deleted file mode 100644 index 44c11416fb..0000000000 --- a/mm2src/gossipsub/tests/smoke.rs +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright 2019 Parity Technologies (UK) Ltd. -// -// 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: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// 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. - -use futures::prelude::*; -use log::debug; -use quickcheck::{QuickCheck, TestResult}; -use rand::{random, seq::SliceRandom, SeedableRng}; -use std::{pin::Pin, - task::{Context, Poll}, - time::Duration}; - -use atomicdex_gossipsub::{Gossipsub, GossipsubConfigBuilder, GossipsubEvent, Topic}; -use futures::StreamExt; -use libp2p_core::{identity, multiaddr::Protocol, transport::MemoryTransport, upgrade, Multiaddr, Transport}; -use libp2p_plaintext::PlainText2Config; -use libp2p_swarm::{Swarm, SwarmEvent}; -use libp2p_yamux as yamux; - -struct Graph { - pub nodes: Vec<(Multiaddr, Swarm)>, -} - -impl Future for Graph { - type Output = (Multiaddr, GossipsubEvent); - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - for (addr, node) in &mut self.nodes { - loop { - match node.poll_next_unpin(cx) { - Poll::Ready(Some(SwarmEvent::Behaviour(event))) => return Poll::Ready((addr.clone(), event)), - Poll::Ready(Some(_)) => {}, - Poll::Ready(None) => panic!("unexpected None when polling nodes"), - Poll::Pending => break, - } - } - } - - Poll::Pending - } -} - -impl Graph { - fn new_connected(num_nodes: usize, seed: u64) -> Graph { - if num_nodes == 0 { - panic!("expecting at least one node"); - } - - let mut rng = rand::rngs::StdRng::seed_from_u64(seed); - - let mut not_connected_nodes = std::iter::once(()) - .cycle() - .take(num_nodes) - .map(|_| build_node()) - .collect::)>>(); - - let mut connected_nodes = vec![not_connected_nodes.pop().unwrap()]; - - while !not_connected_nodes.is_empty() { - connected_nodes.shuffle(&mut rng); - not_connected_nodes.shuffle(&mut rng); - - let mut next = not_connected_nodes.pop().unwrap(); - let connected_addr = &connected_nodes[0].0; - - // Memory transport can not handle addresses with `/p2p` suffix. - let mut connected_addr_no_p2p = connected_addr.clone(); - let p2p_suffix_connected = connected_addr_no_p2p.pop(); - - debug!( - "Connect: {} -> {}", - next.0.clone().pop().unwrap(), - p2p_suffix_connected.unwrap() - ); - - Swarm::dial(&mut next.1, connected_addr_no_p2p).unwrap(); - - connected_nodes.push(next); - } - - Graph { nodes: connected_nodes } - } - - /// Polls the graph and passes each event into the provided FnMut until the closure returns - /// `true`. - /// - /// Returns [`true`] on success and [`false`] on timeout. - fn wait_for bool>(&mut self, mut f: F) -> bool { - let fut = futures::future::poll_fn(move |cx| match self.poll_unpin(cx) { - Poll::Ready((_addr, ev)) if f(&ev) => Poll::Ready(()), - _ => Poll::Pending, - }); - - let fut = async_std::future::timeout(Duration::from_secs(10), fut); - - futures::executor::block_on(fut).is_ok() - } - - /// Polls the graph until Poll::Pending is obtained, completing the underlying polls. - fn drain_poll(self) -> Self { - // The future below should return self. Given that it is a FnMut and not a FnOnce, one needs - // to wrap `self` in an Option, leaving a `None` behind after the final `Poll::Ready`. - let mut this = Some(self); - - let fut = futures::future::poll_fn(move |cx| match &mut this { - Some(graph) => loop { - match graph.poll_unpin(cx) { - Poll::Ready(_) => {}, - Poll::Pending => return Poll::Ready(this.take().unwrap()), - } - }, - None => panic!("future called after final return"), - }); - let fut = async_std::future::timeout(Duration::from_secs(10), fut); - futures::executor::block_on(fut).unwrap() - } -} - -fn build_node() -> (Multiaddr, Swarm) { - let key = identity::Keypair::generate_ed25519(); - let public_key = key.public(); - - let transport = MemoryTransport::default() - .upgrade(upgrade::Version::V1) - .authenticate(PlainText2Config { - local_public_key: public_key.clone(), - }) - .multiplex(yamux::YamuxConfig::default()) - .boxed(); - - let peer_id = public_key.to_peer_id(); - - // NOTE: The graph of created nodes can be disconnected from the mesh point of view as nodes - // can reach their d_lo value and not add other nodes to their mesh. To speed up this test, we - // reduce the default values of the heartbeat, so that all nodes will receive gossip in a - // timely fashion. - - let config = GossipsubConfigBuilder::default() - .heartbeat_initial_delay(Duration::from_millis(100)) - .heartbeat_interval(Duration::from_millis(200)) - .history_length(10) - .history_gossip(10) - .build(); - let behaviour = Gossipsub::new(peer_id, config); - let mut swarm = Swarm::new(transport, behaviour, peer_id); - - let port = 1 + random::(); - let mut addr: Multiaddr = Protocol::Memory(port).into(); - swarm.listen_on(addr.clone()).unwrap(); - - addr = addr.with(libp2p_core::multiaddr::Protocol::P2p(public_key.to_peer_id().into())); - - (addr, swarm) -} - -#[test] -fn multi_hop_propagation() { - let _ = env_logger::try_init(); - - fn prop(num_nodes: u8, seed: u64) -> TestResult { - if !(2..=50).contains(&num_nodes) { - return TestResult::discard(); - } - - debug!("number nodes: {:?}, seed: {:?}", num_nodes, seed); - - let mut graph = Graph::new_connected(num_nodes as usize, seed); - let number_nodes = graph.nodes.len(); - - // Subscribe each node to the same topic. - let topic = Topic::new("test-net".into()); - for (_addr, node) in &mut graph.nodes { - node.behaviour_mut().subscribe(topic.clone()); - } - - // Wait for all nodes to be subscribed. - let mut subscribed = 0; - let all_subscribed = graph.wait_for(move |ev| { - if let GossipsubEvent::Subscribed { .. } = ev { - subscribed += 1; - if subscribed == (number_nodes - 1) * 2 { - return true; - } - } - - false - }); - if !all_subscribed { - return TestResult::error(format!( - "Timed out waiting for all nodes to subscribe but only have {:?}/{:?}.", - subscribed, num_nodes, - )); - } - - // It can happen that the publish occurs before all grafts have completed causing this test - // to fail. We drain all the poll messages before publishing. - graph = graph.drain_poll(); - - // Publish a single message. - graph.nodes[0].1.behaviour_mut().publish(&topic, vec![1, 2, 3]); - - // Wait for all nodes to receive the published message. - let mut received_msgs = 0; - let all_received = graph.wait_for(move |ev| { - if let GossipsubEvent::Message { .. } = ev { - received_msgs += 1; - if received_msgs == number_nodes - 1 { - return true; - } - } - - false - }); - if !all_received { - return TestResult::error(format!( - "Timed out waiting for all nodes to receive the msg but only have {:?}/{:?}.", - received_msgs, num_nodes, - )); - } - - TestResult::passed() - } - - QuickCheck::new() - .max_tests(5) - .quickcheck(prop as fn(u8, u64) -> TestResult) -} diff --git a/mm2src/mm2_bin_lib/Cargo.toml b/mm2src/mm2_bin_lib/Cargo.toml index 2d1b1f461a..bf95f316e4 100644 --- a/mm2src/mm2_bin_lib/Cargo.toml +++ b/mm2src/mm2_bin_lib/Cargo.toml @@ -5,15 +5,10 @@ [package] name = "mm2_bin_lib" -version = "2.1.0-beta" -authors = ["James Lee", "Artem Pikulin", "Artem Grinblat", "Omar S.", "Onur Ozkan", "Alina Sharon", "Caglar Kaya", "Cipi", "Sergey Boiko", "Samuel Onoja", "Roman Sztergbaum", "Kadan Stadelmann ", "Dimxy"] +version = "2.3.0-beta" +authors = ["James Lee", "Artem Pikulin", "Artem Grinblat", "Omar S.", "Onur Ozkan", "Alina Sharon", "Caglar Kaya", "Cipi", "Sergey Boiko", "Samuel Onoja", "Roman Sztergbaum", "Kadan Stadelmann ", "Dimxy", "Omer Yacine"] edition = "2018" -default-run = "mm2" - -# wasm-opt reduces the size from 17 Mb to 14. But it runs for few minutes, which is not good for CI. -# For production builds, it's recommended to run wasm-opt separately. -[package.metadata.wasm-pack.profile.release] -wasm-opt = false +default-run = "kdf" [features] custom-swap-locktime = ["mm2_main/custom-swap-locktime"] # only for testing purposes, should never be activated on release builds. @@ -28,8 +23,15 @@ test = false doctest = false bench = false +[[bin]] +name = "kdf" +path = "src/mm2_bin.rs" +test = false +doctest = false +bench = false + [lib] -name = "mm2lib" +name = "kdflib" crate-type = ["cdylib"] test = false doctest = false diff --git a/mm2src/mm2_bin_lib/src/lib.rs b/mm2src/mm2_bin_lib/src/lib.rs index 008f0d775c..7ac292aa63 100644 --- a/mm2src/mm2_bin_lib/src/lib.rs +++ b/mm2src/mm2_bin_lib/src/lib.rs @@ -1,6 +1,6 @@ use enum_primitive_derive::Primitive; use mm2_core::mm_ctx::MmArc; -use mm2_main::mm2::lp_dispatcher::{dispatch_lp_event, StopCtxEvent}; +use mm2_main::lp_dispatcher::{dispatch_lp_event, StopCtxEvent}; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -41,7 +41,7 @@ fn mm2_status() -> MainStatus { Err(_) => return MainStatus::NoRpc, }; - if ctx.rpc_started.copy_or(false) { + if *ctx.rpc_started.get().unwrap_or(&false) { MainStatus::RpcIsUp } else { MainStatus::NoRpc @@ -99,5 +99,5 @@ fn prepare_for_mm2_stop() -> PrepareForStopResult { async fn finalize_mm2_stop(ctx: MmArc) { dispatch_lp_event(ctx.clone(), StopCtxEvent.into()).await; - let _ = ctx.stop(); + let _ = ctx.stop().await; } diff --git a/mm2src/mm2_bin_lib/src/mm2_bin.rs b/mm2src/mm2_bin_lib/src/mm2_bin.rs index 053d9dd9b4..1587103cc4 100644 --- a/mm2src/mm2_bin_lib/src/mm2_bin.rs +++ b/mm2src/mm2_bin_lib/src/mm2_bin.rs @@ -1,4 +1,4 @@ -#[cfg(not(target_arch = "wasm32"))] use mm2_main::mm2::mm2_main; +#[cfg(not(target_arch = "wasm32"))] use mm2_main::mm2_main; #[cfg(not(target_arch = "wasm32"))] const MM_VERSION: &str = env!("MM_VERSION"); diff --git a/mm2src/mm2_bin_lib/src/mm2_native_lib.rs b/mm2src/mm2_bin_lib/src/mm2_native_lib.rs index 5f85596311..17d7a839bc 100644 --- a/mm2src/mm2_bin_lib/src/mm2_native_lib.rs +++ b/mm2src/mm2_bin_lib/src/mm2_native_lib.rs @@ -70,9 +70,7 @@ pub unsafe extern "C" fn mm2_main(conf: *const c_char, log_cb: extern "C" fn(lin return; } let ctx_cb = &|ctx| CTX.store(ctx, Ordering::Relaxed); - match catch_unwind(move || { - mm2_main::mm2::run_lp_main(Some(&conf), ctx_cb, MM_VERSION.into(), MM_DATETIME.into()) - }) { + match catch_unwind(move || mm2_main::run_lp_main(Some(&conf), ctx_cb, MM_VERSION.into(), MM_DATETIME.into())) { Ok(Ok(_)) => log!("run_lp_main finished"), Ok(Err(err)) => log!("run_lp_main error: {}", err), Err(err) => log!("run_lp_main panic: {:?}", any_to_str(&*err)), @@ -125,7 +123,7 @@ pub extern "C" fn mm2_test(torch: i32, log_cb: extern "C" fn(line: *const c_char }, }; let conf = json::to_string(&ctx.conf).unwrap(); - let hy_res = mm2_main::mm2::rpc::lp_commands_legacy::stop(ctx); + let hy_res = mm2_main::rpc::lp_commands::legacy::stop(ctx); let r = match block_on(hy_res) { Ok(r) => r, Err(err) => { diff --git a/mm2src/mm2_bin_lib/src/mm2_wasm_lib.rs b/mm2src/mm2_bin_lib/src/mm2_wasm_lib.rs index 4cafd6509e..f878e1b914 100644 --- a/mm2src/mm2_bin_lib/src/mm2_wasm_lib.rs +++ b/mm2src/mm2_bin_lib/src/mm2_wasm_lib.rs @@ -16,7 +16,7 @@ use super::*; use common::log::{register_callback, LogLevel, WasmCallback}; use common::{console_err, console_info, deserialize_from_js, executor, serialize_to_js, set_panic_hook}; use enum_primitive_derive::Primitive; -use mm2_main::mm2::LpMainParams; +use mm2_main::LpMainParams; use mm2_rpc::data::legacy::MmVersionResponse; use mm2_rpc::wasm_rpc::WasmRpcResponse; use serde::{Deserialize, Serialize}; @@ -110,12 +110,12 @@ pub fn mm2_main(params: JsValue, log_cb: js_sys::Function) -> Result<(), JsValue let ctx_cb = |ctx| CTX.store(ctx, Ordering::Relaxed); // TODO figure out how to use catch_unwind here // use futures::FutureExt; - // match mm2::lp_main(params, &ctx_cb).catch_unwind().await { + // match mm2_main::lp_main(params, &ctx_cb).catch_unwind().await { // Ok(Ok(_)) => console_info!("run_lp_main finished"), // Ok(Err(err)) => console_err!("run_lp_main error: {}", err), // Err(err) => console_err!("run_lp_main panic: {:?}", any_to_str(&*err)), // }; - match mm2_main::mm2::lp_main(params, &ctx_cb, MM_VERSION.into(), MM_DATETIME.into()).await { + match mm2_main::lp_main(params, &ctx_cb, MM_VERSION.into(), MM_DATETIME.into()).await { Ok(()) => console_info!("run_lp_main finished"), Err(err) => console_err!("run_lp_main error: {}", err), }; @@ -216,7 +216,7 @@ pub async fn mm2_rpc(payload: JsValue) -> Result { Err(_) => return Err(Mm2RpcErr::NotRunning.into()), }; - let wasm_rpc = ctx.wasm_rpc.ok_or(JsValue::from(Mm2RpcErr::NotRunning))?; + let wasm_rpc = ctx.wasm_rpc.get().ok_or(JsValue::from(Mm2RpcErr::NotRunning))?; let response: Mm2RpcResponse = wasm_rpc.request(request_json).await.into(); serialize_to_js(&response).map_err(|e| { diff --git a/mm2src/mm2_bitcoin/chain/src/transaction.rs b/mm2src/mm2_bitcoin/chain/src/transaction.rs index 0490447d29..805b32caf7 100644 --- a/mm2src/mm2_bitcoin/chain/src/transaction.rs +++ b/mm2src/mm2_bitcoin/chain/src/transaction.rs @@ -248,16 +248,13 @@ impl From for ExtTransaction { } #[allow(clippy::upper_case_acronyms)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum TxHashAlgo { + #[default] DSHA256, SHA256, } -impl Default for TxHashAlgo { - fn default() -> Self { TxHashAlgo::DSHA256 } -} - /// Represents the error returned when transaction has no outputs #[derive(Debug)] pub struct TxHasNoOutputs {} diff --git a/mm2src/mm2_bitcoin/crypto/src/lib.rs b/mm2src/mm2_bitcoin/crypto/src/lib.rs index f8e6e59388..393712ccb1 100644 --- a/mm2src/mm2_bitcoin/crypto/src/lib.rs +++ b/mm2src/mm2_bitcoin/crypto/src/lib.rs @@ -21,17 +21,14 @@ use std::hash::Hasher; /// GRS uses double groestl512 /// SMART uses keccak #[allow(clippy::upper_case_acronyms)] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] pub enum ChecksumType { + #[default] DSHA256, DGROESTL512, KECCAK256, } -impl Default for ChecksumType { - fn default() -> ChecksumType { ChecksumType::DSHA256 } -} - /// RIPEMD160 #[inline] pub fn ripemd160(input: &[u8]) -> H160 { diff --git a/mm2src/mm2_bitcoin/keys/src/address.rs b/mm2src/mm2_bitcoin/keys/src/address.rs index 61d262245d..224b886bb4 100644 --- a/mm2src/mm2_bitcoin/keys/src/address.rs +++ b/mm2src/mm2_bitcoin/keys/src/address.rs @@ -43,13 +43,14 @@ pub enum AddressScriptType { P2WSH, } -#[derive(Clone, Debug, Display, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Display, Deserialize, Eq, Hash, PartialEq, Serialize)] #[serde(tag = "format")] pub enum AddressFormat { /// Standard UTXO address format. /// In Bitcoin Cash context the standard format also known as 'legacy'. #[serde(rename = "standard")] #[display(fmt = "Legacy")] + #[default] Standard, /// Segwit Address /// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki @@ -68,10 +69,6 @@ pub enum AddressFormat { }, } -impl Default for AddressFormat { - fn default() -> Self { AddressFormat::Standard } -} - impl AddressFormat { pub fn is_segwit(&self) -> bool { matches!(*self, AddressFormat::Segwit) } diff --git a/mm2src/mm2_bitcoin/keys/src/keypair.rs b/mm2src/mm2_bitcoin/keys/src/keypair.rs index 0e29150f60..4c3abdb8f4 100644 --- a/mm2src/mm2_bitcoin/keys/src/keypair.rs +++ b/mm2src/mm2_bitcoin/keys/src/keypair.rs @@ -124,7 +124,7 @@ mod tests { fn check_sign(secret: &'static str, raw_message: &[u8], signature: &'static str) -> bool { let message = dhash256(raw_message); let kp = KeyPair::from_private(secret.into()).unwrap(); - kp.private().sign(&message).unwrap() == signature.into() + kp.private().sign_low_r(&message).unwrap() == signature.into() } fn check_verify(secret: &'static str, raw_message: &[u8], signature: &'static str) -> bool { diff --git a/mm2src/mm2_bitcoin/keys/src/private.rs b/mm2src/mm2_bitcoin/keys/src/private.rs index aeb60a7711..ce4e64a908 100644 --- a/mm2src/mm2_bitcoin/keys/src/private.rs +++ b/mm2src/mm2_bitcoin/keys/src/private.rs @@ -27,7 +27,16 @@ impl Private { pub fn sign(&self, message: &Message) -> Result { let secret = SecretKey::from_slice(&*self.secret)?; let message = SecpMessage::from_slice(&**message)?; - // use low R signing from bitcoin which reduces signature malleability + let signature = SECP_SIGN.sign(&message, &secret); + let data = signature.serialize_der(); + Ok(data.as_ref().to_vec().into()) + } + + /// Sign a message with a low R value, this reduces signature malleability for Bitcoin transactions + /// and makes fee estimation more reliable. + pub fn sign_low_r(&self, message: &Message) -> Result { + let secret = SecretKey::from_slice(&*self.secret)?; + let message = SecpMessage::from_slice(&**message)?; let signature = SECP_SIGN.sign_low_r(&message, &secret); let data = signature.serialize_der(); Ok(data.as_ref().to_vec().into()) diff --git a/mm2src/mm2_bitcoin/rpc/src/lib.rs b/mm2src/mm2_bitcoin/rpc/src/lib.rs index d901dbdb22..70efe15dc7 100644 --- a/mm2src/mm2_bitcoin/rpc/src/lib.rs +++ b/mm2src/mm2_bitcoin/rpc/src/lib.rs @@ -1,4 +1,3 @@ -extern crate core; #[cfg(test)] extern crate lazy_static; extern crate log; extern crate rustc_hex as hex; diff --git a/mm2src/mm2_bitcoin/rpc/src/v1/types/address.rs b/mm2src/mm2_bitcoin/rpc/src/v1/types/address.rs index 90eeeefc00..bd8af944ac 100644 --- a/mm2src/mm2_bitcoin/rpc/src/v1/types/address.rs +++ b/mm2src/mm2_bitcoin/rpc/src/v1/types/address.rs @@ -61,7 +61,7 @@ pub mod vec { { as Deserialize>::deserialize(deserializer)? .into_iter() - .map(|value| AddressVisitor::default().visit_str(&value)) + .map(|value| AddressVisitor.visit_str(&value)) .collect() } } diff --git a/mm2src/mm2_bitcoin/rpc/src/v1/types/bytes.rs b/mm2src/mm2_bitcoin/rpc/src/v1/types/bytes.rs index 2305264580..5368985a08 100644 --- a/mm2src/mm2_bitcoin/rpc/src/v1/types/bytes.rs +++ b/mm2src/mm2_bitcoin/rpc/src/v1/types/bytes.rs @@ -1,8 +1,9 @@ +//! Serializable wrapper around vector of bytes + use hex::{FromHex, ToHex}; use primitives::bytes::Bytes as GlobalBytes; use serde::de::{Error, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -///! Serializable wrapper around vector of bytes use std::{fmt, ops}; /// Wrapper structure around vector of bytes. @@ -84,8 +85,8 @@ impl ops::Deref for Bytes { fn deref(&self) -> &Self::Target { &self.0 } } -impl ::core::fmt::LowerHex for Bytes { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { +impl ::std::fmt::LowerHex for Bytes { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { for i in &self.0[..] { write!(f, "{:02x}", i)?; } diff --git a/mm2src/mm2_bitcoin/rpc/src/v1/types/hash.rs b/mm2src/mm2_bitcoin/rpc/src/v1/types/hash.rs index 1911d11781..00ecf4f52f 100644 --- a/mm2src/mm2_bitcoin/rpc/src/v1/types/hash.rs +++ b/mm2src/mm2_bitcoin/rpc/src/v1/types/hash.rs @@ -147,8 +147,8 @@ macro_rules! impl_hash { } } - impl ::core::fmt::LowerHex for $name { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + impl ::std::fmt::LowerHex for $name { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { for i in &self.0[..] { write!(f, "{:02x}", i)?; } diff --git a/mm2src/mm2_bitcoin/script/src/sign.rs b/mm2src/mm2_bitcoin/script/src/sign.rs index a560b22077..a70e423eea 100644 --- a/mm2src/mm2_bitcoin/script/src/sign.rs +++ b/mm2src/mm2_bitcoin/script/src/sign.rs @@ -262,7 +262,7 @@ impl TransactionInputSigner { ) -> TransactionInput { let hash = self.signature_hash(input_index, input_amount, script_pubkey, sigversion, sighash); - let mut signature: Vec = keypair.private().sign(&hash).unwrap().into(); + let mut signature: Vec = keypair.private().sign_low_r(&hash).unwrap().into(); signature.push(sighash as u8); let script_sig = Builder::default() .push_data(&signature) diff --git a/mm2src/mm2_core/Cargo.toml b/mm2src/mm2_core/Cargo.toml index 943dcac280..78fffea53b 100644 --- a/mm2src/mm2_core/Cargo.toml +++ b/mm2src/mm2_core/Cargo.toml @@ -15,8 +15,10 @@ common = { path = "../common" } db_common = { path = "../db_common" } derive_more = "0.99" futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } +gstuff = { version = "0.7", features = ["nightly"] } hex = "0.4.2" lazy_static = "1.4" +libp2p = { git = "https://github.com/KomodoPlatform/rust-libp2p.git", tag = "k-0.52.4", default-features = false, features = ["identify"] } mm2_err_handle = { path = "../mm2_err_handle" } mm2_event_stream = { path = "../mm2_event_stream" } mm2_metrics = { path = "../mm2_metrics" } @@ -30,13 +32,11 @@ shared_ref_counter = { path = "../common/shared_ref_counter" } uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } [target.'cfg(target_arch = "wasm32")'.dependencies] -gstuff = { version = "0.7", features = ["nightly"] } instant = { version = "0.1.12", features = ["wasm-bindgen"] } mm2_rpc = { path = "../mm2_rpc", features = [ "rpc_facilities" ] } wasm-bindgen-test = { version = "0.3.2" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] rustls = { version = "0.21", default-features = false } -gstuff = { version = "0.7", features = ["nightly"] } instant = "0.1.12" tokio = { version = "1.20", features = ["io-util", "rt-multi-thread", "net"] } diff --git a/mm2src/mm2_core/src/data_asker.rs b/mm2src/mm2_core/src/data_asker.rs index 5d01310630..7f32f93365 100644 --- a/mm2src/mm2_core/src/data_asker.rs +++ b/mm2src/mm2_core/src/data_asker.rs @@ -113,8 +113,6 @@ pub async fn send_asked_data_rpc( asked_data: SendAskedDataRequest, ) -> Result> { let mut awaiting_asks = ctx.data_asker.awaiting_asks.lock().await; - awaiting_asks.clear_expired_entries(); - match awaiting_asks.remove(&asked_data.data_id) { Some(sender) => { sender.send(asked_data.data).map_to_mm(|_| { diff --git a/mm2src/mm2_core/src/event_dispatcher.rs b/mm2src/mm2_core/src/event_dispatcher.rs index 479ca7d616..6c519a75e8 100644 --- a/mm2src/mm2_core/src/event_dispatcher.rs +++ b/mm2src/mm2_core/src/event_dispatcher.rs @@ -132,7 +132,7 @@ mod event_dispatcher_tests { #[test] fn test_dispatcher() { let mut dispatcher: Dispatcher = Default::default(); - let listener = ListenerSwapStatusChanged::default(); + let listener = ListenerSwapStatusChanged; let res = ListenerSwapStatusChangedArc(Arc::new(listener)); dispatcher.add_listener(res.clone()); assert_eq!(dispatcher.nb_listeners(), 1); diff --git a/mm2src/mm2_core/src/lib.rs b/mm2src/mm2_core/src/lib.rs index 98425279c2..88b0fd5c81 100644 --- a/mm2src/mm2_core/src/lib.rs +++ b/mm2src/mm2_core/src/lib.rs @@ -1,25 +1,27 @@ -use derive_more::Display; -use rand::{thread_rng, Rng}; +#[cfg(target_arch = "wasm32")] use derive_more::Display; +#[cfg(target_arch = "wasm32")] use rand::{thread_rng, Rng}; pub mod data_asker; pub mod event_dispatcher; pub mod mm_ctx; -#[derive(Clone, Copy, Display, PartialEq)] +#[cfg(target_arch = "wasm32")] +#[derive(Clone, Copy, Display, PartialEq, Default)] pub enum DbNamespaceId { #[display(fmt = "MAIN")] + #[default] Main, #[display(fmt = "TEST_{}", _0)] Test(u64), } -impl Default for DbNamespaceId { - fn default() -> Self { DbNamespaceId::Main } -} - +#[cfg(target_arch = "wasm32")] impl DbNamespaceId { pub fn for_test() -> DbNamespaceId { let mut rng = thread_rng(); DbNamespaceId::Test(rng.gen()) } + + #[inline(always)] + pub fn for_test_with_id(id: u64) -> DbNamespaceId { DbNamespaceId::Test(id) } } diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index af0a6adece..0a1afb2eea 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -1,11 +1,15 @@ #[cfg(feature = "track-ctx-pointer")] use common::executor::Timer; -use common::executor::{abortable_queue::{AbortableQueue, WeakSpawner}, - graceful_shutdown, AbortSettings, AbortableSystem, SpawnAbortable, SpawnFuture}; use common::log::{self, LogLevel, LogOnError, LogState}; use common::{cfg_native, cfg_wasm32, small_rng}; -use gstuff::{try_s, Constructible, ERR, ERRL}; +use common::{executor::{abortable_queue::{AbortableQueue, WeakSpawner}, + graceful_shutdown, AbortSettings, AbortableSystem, SpawnAbortable, SpawnFuture}, + expirable_map::ExpirableMap}; +use futures::channel::oneshot; +use futures::lock::Mutex as AsyncMutex; +use gstuff::{try_s, ERR, ERRL}; use lazy_static::lazy_static; +use libp2p::PeerId; use mm2_event_stream::{controller::Controller, Event, EventStreamConfiguration}; use mm2_metrics::{MetricsArc, MetricsOps}; use primitives::hash::H160; @@ -18,7 +22,7 @@ use std::collections::HashSet; use std::fmt; use std::future::Future; use std::ops::Deref; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, OnceLock}; use crate::data_asker::DataAsker; @@ -30,7 +34,6 @@ cfg_wasm32! { cfg_native! { use db_common::async_sql_conn::AsyncConnection; use db_common::sqlite::rusqlite::Connection; - use futures::lock::Mutex as AsyncMutex; use rustls::ServerName; use mm2_metrics::prometheus; use mm2_metrics::MmMetricsError; @@ -73,9 +76,9 @@ pub struct MmCtx { /// Should be refactored away in the future. State should always be valid. /// If there are things that are loaded in background then they should be separately optional, /// without invalidating the entire state. - pub initialized: Constructible, + pub initialized: OnceLock, /// True if the RPC HTTP server was started. - pub rpc_started: Constructible, + pub rpc_started: OnceLock, /// Controller for continuously streaming data using streaming channels of `mm2_event_stream`. pub stream_channel_controller: Controller, /// Data transfer bridge between server and client where server (which is the mm2 runtime) initiates the request. @@ -83,10 +86,10 @@ pub struct MmCtx { /// Configuration of event streaming used for SSE. pub event_stream_configuration: Option, /// True if the MarketMaker instance needs to stop. - pub stop: Constructible, + pub stop: OnceLock, /// Unique context identifier, allowing us to more easily pass the context through the FFI boundaries. /// 0 if the handler ID is allocated yet. - pub ffi_handle: Constructible, + pub ffi_handle: OnceLock, /// The context belonging to the `ordermatch` mod: `OrdermatchContext`. pub ordermatch_ctx: Mutex>>, pub rate_limit_ctx: Mutex>>, @@ -94,7 +97,6 @@ pub struct MmCtx { pub dispatcher_ctx: Mutex>>, pub message_service_ctx: Mutex>>, pub p2p_ctx: Mutex>>, - pub peer_id: Constructible, pub account_ctx: Mutex>>, /// The context belonging to the `coins` crate: `CoinsContext`. pub coins_ctx: Mutex>>, @@ -102,10 +104,10 @@ pub struct MmCtx { pub crypto_ctx: Mutex>>, /// RIPEMD160(SHA256(x)) where x is secp256k1 pubkey derived from passphrase. /// This hash is **unique** among Iguana and each HD accounts derived from the same passphrase. - pub rmd160: Constructible, + pub rmd160: OnceLock, /// A shared DB identifier - RIPEMD160(SHA256(x)) where x is secp256k1 pubkey derived from (passphrase + magic salt). /// This hash is **the same** for Iguana and all HD accounts derived from the same passphrase. - pub shared_db_id: Constructible, + pub shared_db_id: OnceLock, /// Coins that should be enabled to kick start the interrupted swaps and orders. pub coins_needed_for_kick_start: Mutex>, /// The context belonging to the `lp_swap` mod: `SwapsContext`. @@ -113,19 +115,19 @@ pub struct MmCtx { /// The context belonging to the `lp_stats` mod: `StatsContext` pub stats_ctx: Mutex>>, /// Wallet name for this mm2 instance. Optional for backwards compatibility. - pub wallet_name: Constructible>, + pub wallet_name: OnceLock>, /// The context belonging to the `lp_wallet` mod: `WalletsContext`. #[cfg(target_arch = "wasm32")] pub wallets_ctx: Mutex>>, /// The RPC sender forwarding requests to writing part of underlying stream. #[cfg(target_arch = "wasm32")] - pub wasm_rpc: Constructible, + pub wasm_rpc: OnceLock, /// Deprecated, please use `async_sqlite_connection` for new implementations. #[cfg(not(target_arch = "wasm32"))] - pub sqlite_connection: Constructible>>, + pub sqlite_connection: OnceLock>>, /// Deprecated, please create `shared_async_sqlite_conn` for new implementations and call db `KOMODEFI-shared.db`. #[cfg(not(target_arch = "wasm32"))] - pub shared_sqlite_conn: Constructible>>, + pub shared_sqlite_conn: OnceLock>>, pub mm_version: String, pub datetime: String, pub mm_init_ctx: Mutex>>, @@ -142,7 +144,9 @@ pub struct MmCtx { pub nft_ctx: Mutex>>, /// asynchronous handle for rusqlite connection. #[cfg(not(target_arch = "wasm32"))] - pub async_sqlite_connection: Constructible>>, + pub async_sqlite_connection: OnceLock>>, + /// Links the RPC context to the P2P context to handle health check responses. + pub healthcheck_response_handler: AsyncMutex>>, } impl MmCtx { @@ -151,38 +155,37 @@ impl MmCtx { conf: Json::Object(json::Map::new()), log: log::LogArc::new(log), metrics: MetricsArc::new(), - initialized: Constructible::default(), - rpc_started: Constructible::default(), + initialized: OnceLock::default(), + rpc_started: OnceLock::default(), stream_channel_controller: Controller::new(), data_asker: DataAsker::default(), event_stream_configuration: None, - stop: Constructible::default(), - ffi_handle: Constructible::default(), + stop: OnceLock::default(), + ffi_handle: OnceLock::default(), ordermatch_ctx: Mutex::new(None), rate_limit_ctx: Mutex::new(None), simple_market_maker_bot_ctx: Mutex::new(None), dispatcher_ctx: Mutex::new(None), message_service_ctx: Mutex::new(None), p2p_ctx: Mutex::new(None), - peer_id: Constructible::default(), account_ctx: Mutex::new(None), coins_ctx: Mutex::new(None), coins_activation_ctx: Mutex::new(None), crypto_ctx: Mutex::new(None), - rmd160: Constructible::default(), - shared_db_id: Constructible::default(), + rmd160: OnceLock::default(), + shared_db_id: OnceLock::default(), coins_needed_for_kick_start: Mutex::new(HashSet::new()), swaps_ctx: Mutex::new(None), stats_ctx: Mutex::new(None), - wallet_name: Constructible::default(), + wallet_name: OnceLock::default(), #[cfg(target_arch = "wasm32")] wallets_ctx: Mutex::new(None), #[cfg(target_arch = "wasm32")] - wasm_rpc: Constructible::default(), + wasm_rpc: OnceLock::default(), #[cfg(not(target_arch = "wasm32"))] - sqlite_connection: Constructible::default(), + sqlite_connection: OnceLock::default(), #[cfg(not(target_arch = "wasm32"))] - shared_sqlite_conn: Constructible::default(), + shared_sqlite_conn: OnceLock::default(), mm_version: "".into(), datetime: "".into(), mm_init_ctx: Mutex::new(None), @@ -192,7 +195,8 @@ impl MmCtx { db_namespace: DbNamespaceId::Main, nft_ctx: Mutex::new(None), #[cfg(not(target_arch = "wasm32"))] - async_sqlite_connection: Constructible::default(), + async_sqlite_connection: OnceLock::default(), + healthcheck_response_handler: AsyncMutex::new(ExpirableMap::default()), } } @@ -200,14 +204,14 @@ impl MmCtx { lazy_static! { static ref DEFAULT: H160 = [0; 20].into(); } - self.rmd160.or(&|| &*DEFAULT) + self.rmd160.get().unwrap_or(&*DEFAULT) } pub fn shared_db_id(&self) -> &H160 { lazy_static! { static ref DEFAULT: H160 = [0; 20].into(); } - self.shared_db_id.or(&|| &*DEFAULT) + self.shared_db_id.get().unwrap_or(&*DEFAULT) } #[cfg(not(target_arch = "wasm32"))] @@ -289,10 +293,12 @@ impl MmCtx { }) } + /// Returns the path to the MM databases root. + #[cfg(not(target_arch = "wasm32"))] + pub fn db_root(&self) -> PathBuf { path_to_db_root(self.conf["dbdir"].as_str()) } #[cfg(not(target_arch = "wasm32"))] pub fn wallet_file_path(&self, wallet_name: &str) -> PathBuf { - let db_root = path_to_db_root(self.conf["dbdir"].as_str()); - db_root.join(wallet_name.to_string() + ".dat") + self.db_root().join(wallet_name.to_string() + ".dat") } /// MM database path. @@ -340,7 +346,7 @@ impl MmCtx { pub fn spawner(&self) -> MmFutSpawner { MmFutSpawner::new(&self.abortable_system) } /// True if the MarketMaker instance needs to stop. - pub fn is_stopping(&self) -> bool { self.stop.copy_or(false) } + pub fn is_stopping(&self) -> bool { *self.stop.get().unwrap_or(&false) } pub fn gui(&self) -> Option<&str> { self.conf["gui"].as_str() } @@ -351,7 +357,10 @@ impl MmCtx { let sqlite_file_path = self.dbdir().join("MM2.db"); log_sqlite_file_open_attempt(&sqlite_file_path); let connection = try_s!(Connection::open(sqlite_file_path)); - try_s!(self.sqlite_connection.pin(Arc::new(Mutex::new(connection)))); + try_s!(self + .sqlite_connection + .set(Arc::new(Mutex::new(connection))) + .map_err(|_| "Already initialized".to_string())); Ok(()) } @@ -360,7 +369,10 @@ impl MmCtx { let sqlite_file_path = self.shared_dbdir().join("MM2-shared.db"); log_sqlite_file_open_attempt(&sqlite_file_path); let connection = try_s!(Connection::open(sqlite_file_path)); - try_s!(self.shared_sqlite_conn.pin(Arc::new(Mutex::new(connection)))); + try_s!(self + .shared_sqlite_conn + .set(Arc::new(Mutex::new(connection))) + .map_err(|_| "Already initialized".to_string())); Ok(()) } @@ -369,19 +381,23 @@ impl MmCtx { let sqlite_file_path = self.dbdir().join("KOMODEFI.db"); log_sqlite_file_open_attempt(&sqlite_file_path); let async_conn = try_s!(AsyncConnection::open(sqlite_file_path).await); - try_s!(self.async_sqlite_connection.pin(Arc::new(AsyncMutex::new(async_conn)))); + try_s!(self + .async_sqlite_connection + .set(Arc::new(AsyncMutex::new(async_conn))) + .map_err(|_| "Already initialized".to_string())); Ok(()) } #[cfg(not(target_arch = "wasm32"))] pub fn sqlite_conn_opt(&self) -> Option> { - self.sqlite_connection.as_option().map(|conn| conn.lock().unwrap()) + self.sqlite_connection.get().map(|conn| conn.lock().unwrap()) } #[cfg(not(target_arch = "wasm32"))] pub fn sqlite_connection(&self) -> MutexGuard { self.sqlite_connection - .or(&|| panic!("sqlite_connection is not initialized")) + .get() + .expect("sqlite_connection is not initialized") .lock() .unwrap() } @@ -389,7 +405,8 @@ impl MmCtx { #[cfg(not(target_arch = "wasm32"))] pub fn shared_sqlite_conn(&self) -> MutexGuard { self.shared_sqlite_conn - .or(&|| panic!("shared_sqlite_conn is not initialized")) + .get() + .expect("shared_sqlite_conn is not initialized") .lock() .unwrap() } @@ -403,7 +420,7 @@ impl Drop for MmCtx { fn drop(&mut self) { let ffi_handle = self .ffi_handle - .as_option() + .get() .map(|handle| handle.to_string()) .unwrap_or_else(|| "UNKNOWN".to_owned()); log::info!("MmCtx ({}) has been dropped", ffi_handle) @@ -502,8 +519,11 @@ lazy_static! { impl MmArc { pub fn new(ctx: MmCtx) -> MmArc { MmArc(SharedRc::new(ctx)) } - pub fn stop(&self) -> Result<(), String> { - try_s!(self.stop.pin(true)); + pub async fn stop(&self) -> Result<(), String> { + #[cfg(not(target_arch = "wasm32"))] + try_s!(self.close_async_connection().await); + + try_s!(self.stop.set(true)); // Notify shutdown listeners. self.graceful_shutdown_registry.abort_all().warn_log(); @@ -516,6 +536,16 @@ impl MmArc { Ok(()) } + #[cfg(not(target_arch = "wasm32"))] + async fn close_async_connection(&self) -> Result<(), db_common::async_sql_conn::AsyncConnError> { + if let Some(async_conn) = self.async_sqlite_connection.get() { + let mut conn = async_conn.lock().await; + conn.close().await?; + } + + Ok(()) + } + #[cfg(feature = "track-ctx-pointer")] fn track_ctx_pointer(&self) { let ctx_weak = self.weak(); @@ -541,7 +571,7 @@ impl MmArc { /// Unique context identifier, allowing us to more easily pass the context through the FFI boundaries. pub fn ffi_handle(&self) -> Result { let mut mm_ctx_ffi = try_s!(MM_CTX_FFI.lock()); - if let Some(have) = self.ffi_handle.as_option() { + if let Some(have) = self.ffi_handle.get() { return Ok(*have); } let mut tries = 0; @@ -560,7 +590,7 @@ impl MmArc { Entry::Occupied(_) => continue, // Try another ID. Entry::Vacant(ve) => { ve.insert(self.weak()); - try_s!(self.ffi_handle.pin(rid)); + try_s!(self.ffi_handle.set(rid)); return Ok(rid); }, } @@ -679,25 +709,19 @@ impl SpawnAbortable for MmFutSpawner { /// /// * `ctx_field` - A dedicated crate context field in `MmCtx`, such as the `MmCtx::portfolio_ctx`. /// * `constructor` - Generates the initial crate context. -pub fn from_ctx( - ctx_field: &Mutex>>, - constructor: C, -) -> Result, String> +pub fn from_ctx(ctx: &Mutex>>, init: F) -> Result, String> where - C: FnOnce() -> Result, T: 'static + Send + Sync, + F: FnOnce() -> Result, { - let mut ctx_field = try_s!(ctx_field.lock()); - if let Some(ref ctx) = *ctx_field { - let ctx: Arc = match ctx.clone().downcast() { - Ok(p) => p, - Err(_) => return ERR!("Error casting the context field"), - }; - return Ok(ctx); + let mut guard = try_s!(ctx.lock()); + if let Some(ctx) = guard.as_ref() { + return ctx.clone().downcast().map_err(|_| "Context type mismatch".to_string()); } - let arc = Arc::new(try_s!(constructor())); - *ctx_field = Some(arc.clone()); - Ok(arc) + + let new_ctx = Arc::new(init()?); + *guard = Some(new_ctx.clone()); + Ok(new_ctx) } #[derive(Default)] @@ -739,6 +763,12 @@ impl MmCtxBuilder { self } + #[cfg(target_arch = "wasm32")] + pub fn with_test_db_namespace_with_id(mut self, id: u64) -> Self { + self.db_namespace = DbNamespaceId::for_test_with_id(id); + self + } + pub fn into_mm_arc(self) -> MmArc { // NB: We avoid recreating LogState // in order not to interfere with the integration tests checking LogState drop on shutdown. diff --git a/mm2src/mm2_db/src/indexed_db/drivers/upgrader.rs b/mm2src/mm2_db/src/indexed_db/drivers/upgrader.rs index 8f51a220c1..fa55d87d8c 100644 --- a/mm2src/mm2_db/src/indexed_db/drivers/upgrader.rs +++ b/mm2src/mm2_db/src/indexed_db/drivers/upgrader.rs @@ -29,6 +29,8 @@ pub enum OnUpgradeError { old_version: u32, new_version: u32, }, + #[display(fmt = "Error occurred due to deleting the '{}' index: {}", index, description)] + ErrorDeletingIndex { index: String, description: String }, } pub struct DbUpgrader { @@ -108,4 +110,18 @@ impl TableUpgrader { description: stringify_js_error(&e), }) } + + /// Deletes an index. + /// Regardless of whether the index is created using one or multiple fields, the deleteIndex() + /// method is used to delete any type of index, and it works in the same way for both. + /// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/deleteIndex + pub fn delete_index(&self, index: &str) -> OnUpgradeResult<()> { + self.object_store + .delete_index(index) + .map(|_| ()) + .map_to_mm(|e| OnUpgradeError::ErrorDeletingIndex { + index: index.to_owned(), + description: stringify_js_error(&e), + }) + } } diff --git a/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs b/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs index 74b7c3e89b..3f0c1c7b8c 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs @@ -174,7 +174,7 @@ impl<'transaction, 'reference, Table: TableSignature> CursorBuilder<'transaction })?; Ok(CursorIter { event_tx, - phantom: PhantomData::default(), + phantom: PhantomData, }) } } @@ -305,7 +305,6 @@ mod tests { } } - #[track_caller] async fn next_item(cursor_iter: &mut CursorIter<'_, Table>) -> Option { cursor_iter .next() diff --git a/mm2src/mm2_db/src/indexed_db/indexed_db.rs b/mm2src/mm2_db/src/indexed_db/indexed_db.rs index d6e8ab15d4..1595662ad8 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_db.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_db.rs @@ -34,7 +34,7 @@ macro_rules! try_serialize_index_value { return MmError::err(DbTransactionError::ErrorSerializingIndex { index: $index.to_owned(), description: ser_err.to_string(), - }) + }); }, } }}; @@ -210,7 +210,7 @@ impl IndexedDb { let transaction_event_tx = send_event_recv_response(&self.event_tx, event, result_rx).await?; Ok(DbTransaction { event_tx: transaction_event_tx, - phantom: PhantomData::default(), + phantom: PhantomData, }) } @@ -258,7 +258,7 @@ impl DbTransaction<'_> { let transaction_event_tx = send_event_recv_response(&self.event_tx, event, result_rx).await?; Ok(DbTable { event_tx: transaction_event_tx, - phantom: PhantomData::default(), + phantom: PhantomData, }) } @@ -326,6 +326,7 @@ impl AddOrIgnoreResult { } } } + impl<'transaction, Table: TableSignature> DbTable<'transaction, Table> { /// Adds the given item to the table. /// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/add @@ -830,7 +831,10 @@ fn open_cursor( /// Detects the current execution environment (window or worker) and follows the appropriate way /// of getting `web_sys::IdbFactory` instance. pub(crate) fn get_idb_factory() -> Result { - let global = js_sys::global(); + // try getting global with type safety and explicit type conversion. + let global = js_sys::global() + .dyn_into::() + .map_err(|err| InitDbError::NotSupported(format!("{err:?}")))?; let idb_factory = if let Some(window) = global.dyn_ref::() { window.indexed_db() @@ -1216,7 +1220,7 @@ mod tests { .expect("Couldn't get items by the index 'ticker=RICK'"); assert_eq!(actual_rick_txs, vec![ (rick_tx_1_id, rick_tx_1_updated), - (rick_tx_2_id, rick_tx_2) + (rick_tx_2_id, rick_tx_2), ]); } diff --git a/mm2src/mm2_err_handle/src/map_to_mm_fut.rs b/mm2src/mm2_err_handle/src/map_to_mm_fut.rs index 01de1b7d7e..0cfeb9cf13 100644 --- a/mm2src/mm2_err_handle/src/map_to_mm_fut.rs +++ b/mm2src/mm2_err_handle/src/map_to_mm_fut.rs @@ -21,7 +21,7 @@ where /// /// ```rust /// let fut = futures01::future::err("An error".to_owned()); - /// let mapped_res: Result<(), MmError> = fut.map_to_mm_fut(|e| e.len()).wait(); + /// let mapped_res: Result<(), MmError> = block_on_f01(fut.map_to_mm_fut(|e| e.len())); /// ``` #[track_caller] fn map_to_mm_fut(self, f: F) -> MapToMmFuture<'a, T, E1, E2> diff --git a/mm2src/mm2_err_handle/src/mm_error.rs b/mm2src/mm2_err_handle/src/mm_error.rs index 56e7f29f50..1ea7bf1555 100644 --- a/mm2src/mm2_err_handle/src/mm_error.rs +++ b/mm2src/mm2_err_handle/src/mm_error.rs @@ -332,6 +332,7 @@ impl FormattedTrace for Vec { mod tests { use super::*; use crate::prelude::*; + use common::block_on_f01; use futures01::Future; use ser_error_derive::SerializeErrorType; use serde_json::{self as json, json}; @@ -425,10 +426,8 @@ mod tests { } let into_mm_line = line!() + 2; - let mm_err = generate_error("An error") - .map_to_mm_fut(|error| error.len()) - .wait() - .expect_err("Expected an error"); + let mm_err = + block_on_f01(generate_error("An error").map_to_mm_fut(|error| error.len())).expect_err("Expected an error"); assert_eq!(mm_err.etype, 8); assert_eq!(mm_err.trace, vec![TraceLocation::new("mm_error", into_mm_line)]); } diff --git a/mm2src/mm2_gui_storage/src/account/storage/sqlite_storage.rs b/mm2src/mm2_gui_storage/src/account/storage/sqlite_storage.rs index 916854de63..4e2be2acac 100644 --- a/mm2src/mm2_gui_storage/src/account/storage/sqlite_storage.rs +++ b/mm2src/mm2_gui_storage/src/account/storage/sqlite_storage.rs @@ -118,7 +118,7 @@ impl SqliteAccountStorage { pub(crate) fn new(ctx: &MmArc) -> AccountStorageResult { let shared = ctx .sqlite_connection - .as_option() + .get() .or_mm_err(|| AccountStorageError::Internal("'MmCtx::sqlite_connection' is not initialized".to_owned()))?; Ok(SqliteAccountStorage { conn: Arc::clone(shared), diff --git a/mm2src/mm2_io/src/fs.rs b/mm2src/mm2_io/src/fs.rs index e844086d89..489b3bad8b 100644 --- a/mm2src/mm2_io/src/fs.rs +++ b/mm2src/mm2_io/src/fs.rs @@ -9,7 +9,7 @@ use serde::de::DeserializeOwned; use serde::Serialize; use serde_json::{self as json, Error as JsonError}; use std::ffi::OsStr; -use std::fs::{self, DirEntry}; +use std::fs::{self, create_dir_all, DirEntry}; use std::io::{self, Read, Write}; use std::path::{Path, PathBuf}; use std::time::UNIX_EPOCH; @@ -192,19 +192,52 @@ where json::from_slice(&content).map_to_mm(FsJsonError::Deserializing) } -/// Read the `dir_path` entries trying to deserialize each as the `T` type. +async fn filter_files_by_extension(dir_path: &Path, extension: &str) -> IoResult> { + let ext = Some(OsStr::new(extension).to_ascii_lowercase()); + let entries = read_dir_async(dir_path) + .await? + .into_iter() + .filter(|path| path.extension().map(|ext| ext.to_ascii_lowercase()) == ext) + .collect(); + Ok(entries) +} + +/// Helper function to extract file names or stems based on the provided extraction function. +fn extract_file_identifiers<'a, F>(entries: Vec, extractor: F) -> impl Iterator + 'a +where + F: Fn(&Path) -> Option<&OsStr> + 'a, +{ + entries + .into_iter() + .filter_map(move |path| extractor(&path).and_then(OsStr::to_str).map(ToOwned::to_owned)) +} + +/// Lists files by the specified extension from the given directory path. +/// If include_extension is true, returns full file names; otherwise, returns file stems. +pub async fn list_files_by_extension( + dir_path: &Path, + extension: &str, + include_extension: bool, +) -> IoResult> { + let entries = filter_files_by_extension(dir_path, extension).await?; + let extractor = if include_extension { + Path::file_name + } else { + Path::file_stem + }; + Ok(extract_file_identifiers(entries, extractor)) +} + +/// Read the `dir_path` entries trying to deserialize each as the `T` type, +/// filtering by the specified extension. /// Please note that files that couldn't be deserialized are skipped. -pub async fn read_dir_json(dir_path: &Path) -> FsJsonResult> +pub async fn read_files_with_extension(dir_path: &Path, extension: &str) -> FsJsonResult> where T: DeserializeOwned, { - let json_ext = Some(OsStr::new("json")); - let entries: Vec<_> = read_dir_async(dir_path) + let entries = filter_files_by_extension(dir_path, extension) .await - .mm_err(FsJsonError::IoReading)? - .into_iter() - .filter(|path| path.extension() == json_ext) - .collect(); + .mm_err(FsJsonError::IoReading)?; let type_name = std::any::type_name::(); let mut result = Vec::new(); @@ -233,6 +266,16 @@ where Ok(result) } +/// Read the `dir_path` entries trying to deserialize each as the `T` type from JSON files. +/// Please note that files that couldn't be deserialized are skipped. +#[inline(always)] +pub async fn read_dir_json(dir_path: &Path) -> FsJsonResult> +where + T: DeserializeOwned, +{ + read_files_with_extension(dir_path, "json").await +} + pub async fn write_json(t: &T, path: &Path, use_tmp_file: bool) -> FsJsonResult<()> where T: Serialize, @@ -279,3 +322,18 @@ pub fn json_dir_entries(path: &dyn AsRef) -> Result, String> }) .collect()) } + +/// Helper function to copy directories recursively +pub fn copy_dir_all(src: &dyn AsRef, dst: &dyn AsRef) -> io::Result<()> { + create_dir_all(dst)?; + for entry in fs::read_dir(src)? { + let entry = entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + copy_dir_all(&entry.path(), &dst.as_ref().join(entry.file_name()))?; + } else { + std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; + } + } + Ok(()) +} diff --git a/mm2src/mm2_libp2p/Cargo.toml b/mm2src/mm2_libp2p/Cargo.toml deleted file mode 100644 index bd01045799..0000000000 --- a/mm2src/mm2_libp2p/Cargo.toml +++ /dev/null @@ -1,43 +0,0 @@ -[package] -name = "mm2-libp2p" -version = "0.1.0" -authors = ["Artem Pikulin "] -edition = "2018" - -[lib] -doctest = false - -[dependencies] -async-trait = "0.1" -atomicdex-gossipsub = { path = "../gossipsub" } -common = { path = "../common" } -derive_more = "0.99" -libp2p-floodsub = { path = "../floodsub" } -futures = { version = "0.3.1", package = "futures", features = ["compat", "async-await"] } -hex = "0.4.2" -lazy_static = "1.4" -secp256k1 = { version = "0.20", features = ["rand"] } -log = "0.4.17" -rand = { package = "rand", version = "0.7", features = ["std", "wasm-bindgen"] } -regex = "1" -rmp-serde = "0.14.3" -serde = { version = "1.0", features = ["derive"] } -serde_bytes = "0.11.5" -sha2 = "0.10" -void = "1.0" -wasm-timer = "0.2.4" - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -futures-rustls = { version = "0.24" } -tokio = { version = "1.20", features = ["rt-multi-thread", "macros"] } -libp2p = { git = "https://github.com/libp2p/rust-libp2p.git", tag = "v0.45.1", default-features = false, features = ["dns-tokio", "floodsub", "mplex", "noise", "ping", "request-response", "secp256k1", "tcp-tokio", "websocket"] } - -[target.'cfg(target_arch = "wasm32")'.dependencies] -futures-rustls = { version = "0.22" } -libp2p = { git = "https://github.com/libp2p/rust-libp2p.git", tag = "v0.45.1", default-features = false, features = ["floodsub", "mplex", "noise", "ping", "request-response", "secp256k1", "wasm-ext", "wasm-ext-websocket"] } -wasm-bindgen-futures = "0.4.21" - -[dev-dependencies] -async-std = { version = "1.6.2", features = ["unstable"] } -env_logger = "0.9.3" -serde_json = { version = "1", features = ["preserve_order", "raw_value"] } diff --git a/mm2src/mm2_libp2p/src/adex_ping.rs b/mm2src/mm2_libp2p/src/adex_ping.rs deleted file mode 100644 index 73ffb94d83..0000000000 --- a/mm2src/mm2_libp2p/src/adex_ping.rs +++ /dev/null @@ -1,56 +0,0 @@ -use libp2p::swarm::NetworkBehaviour; -use libp2p::{ping::{Ping, PingConfig, PingEvent}, - swarm::{CloseConnection, NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}, - NetworkBehaviour}; -use log::error; -use std::{collections::VecDeque, - num::NonZeroU32, - task::{Context, Poll}}; -use void::Void; - -/// Wrapper around libp2p Ping behaviour that forcefully disconnects a peer using NetworkBehaviourAction::DisconnectPeer -/// event. -/// Libp2p has unclear ConnectionHandlers keep alive logic so in some cases even if Ping handler emits Close event the -/// connection is kept active which is undesirable. -#[derive(NetworkBehaviour)] -#[behaviour(out_event = "Void", event_process = true)] -#[behaviour(poll_method = "poll_event")] -pub struct AdexPing { - ping: Ping, - #[behaviour(ignore)] - events: VecDeque::ConnectionHandler>>, -} - -impl NetworkBehaviourEventProcess for AdexPing { - fn inject_event(&mut self, event: PingEvent) { - if let Err(e) = event.result { - error!("Ping error {}. Disconnecting peer {}", e, event.peer); - self.events.push_back(NetworkBehaviourAction::CloseConnection { - peer_id: event.peer, - connection: CloseConnection::All, - }); - } - } -} - -#[allow(clippy::new_without_default)] -impl AdexPing { - pub fn new() -> Self { - AdexPing { - ping: Ping::new(PingConfig::new().with_max_failures(unsafe { NonZeroU32::new_unchecked(2) })), - events: VecDeque::new(), - } - } - - fn poll_event( - &mut self, - _cx: &mut Context, - _params: &mut impl PollParameters, - ) -> Poll::ConnectionHandler>> { - if let Some(event) = self.events.pop_front() { - return Poll::Ready(event); - } - - Poll::Pending - } -} diff --git a/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs b/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs deleted file mode 100644 index 482251fb93..0000000000 --- a/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs +++ /dev/null @@ -1,984 +0,0 @@ -use crate::{adex_ping::AdexPing, - network::{get_all_network_seednodes, NETID_8762}, - peers_exchange::{PeerAddresses, PeersExchange}, - request_response::{build_request_response_behaviour, PeerRequest, PeerResponse, RequestResponseBehaviour, - RequestResponseBehaviourEvent, RequestResponseSender}, - runtime::SwarmRuntime, - NetworkInfo, NetworkPorts, RelayAddress, RelayAddressError}; -use atomicdex_gossipsub::{Gossipsub, GossipsubConfigBuilder, GossipsubEvent, GossipsubMessage, MessageId, Topic, - TopicHash}; -use common::executor::SpawnFuture; -use derive_more::Display; -use futures::{channel::{mpsc::{channel, Receiver, Sender}, - oneshot}, - future::{join_all, poll_fn}, - Future, FutureExt, SinkExt, StreamExt}; -use futures_rustls::rustls; -use libp2p::core::transport::Boxed as BoxedTransport; -use libp2p::{core::{ConnectedPoint, Multiaddr, Transport}, - identity, - multiaddr::Protocol, - noise, - request_response::ResponseChannel, - swarm::{NetworkBehaviourEventProcess, Swarm}, - NetworkBehaviour, PeerId}; -use libp2p_floodsub::{Floodsub, FloodsubEvent, Topic as FloodsubTopic}; -use log::{debug, error, info}; -use rand::seq::SliceRandom; -use rand::Rng; -use std::{collections::{hash_map::DefaultHasher, BTreeMap}, - hash::{Hash, Hasher}, - iter, - net::IpAddr, - task::{Context, Poll}, - time::Duration}; -use void::Void; -use wasm_timer::{Instant, Interval}; - -pub type AdexCmdTx = Sender; -pub type AdexEventRx = Receiver; - -#[cfg(test)] mod tests; - -pub const PEERS_TOPIC: &str = "PEERS"; -const CONNECTED_RELAYS_CHECK_INTERVAL: Duration = Duration::from_secs(30); -const ANNOUNCE_INTERVAL: Duration = Duration::from_secs(600); -const ANNOUNCE_INITIAL_DELAY: Duration = Duration::from_secs(60); -const CHANNEL_BUF_SIZE: usize = 1024 * 8; - -/// Returns info about connected peers -pub async fn get_peers_info(mut cmd_tx: AdexCmdTx) -> BTreeMap> { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetPeersInfo { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -/// Returns current gossipsub mesh state -pub async fn get_gossip_mesh(mut cmd_tx: AdexCmdTx) -> BTreeMap> { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetGossipMesh { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -pub async fn get_gossip_peer_topics(mut cmd_tx: AdexCmdTx) -> BTreeMap> { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetGossipPeerTopics { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -pub async fn get_gossip_topic_peers(mut cmd_tx: AdexCmdTx) -> BTreeMap> { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetGossipTopicPeers { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -pub async fn get_relay_mesh(mut cmd_tx: AdexCmdTx) -> Vec { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetRelayMesh { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -#[derive(Debug)] -pub struct AdexResponseChannel(ResponseChannel); - -impl From> for AdexResponseChannel { - fn from(res: ResponseChannel) -> Self { AdexResponseChannel(res) } -} - -impl From for ResponseChannel { - fn from(res: AdexResponseChannel) -> Self { res.0 } -} - -#[derive(Debug)] -pub enum AdexBehaviourCmd { - Subscribe { - /// Subscribe to this topic - topic: String, - }, - PublishMsg { - topics: Vec, - msg: Vec, - }, - PublishMsgFrom { - topics: Vec, - msg: Vec, - from: PeerId, - }, - /// Request relays sequential until a response is received. - RequestAnyRelay { - req: Vec, - response_tx: oneshot::Sender)>>, - }, - /// Request given peers and collect all their responses. - RequestPeers { - req: Vec, - peers: Vec, - response_tx: oneshot::Sender>, - }, - /// Request relays and collect all their responses. - RequestRelays { - req: Vec, - response_tx: oneshot::Sender>, - }, - /// Send a response using a `response_channel`. - SendResponse { - /// Response to a request. - res: AdexResponse, - /// Pass the same `response_channel` as that was obtained from [`AdexBehaviourEvent::PeerRequest`]. - response_channel: AdexResponseChannel, - }, - GetPeersInfo { - result_tx: oneshot::Sender>>, - }, - GetGossipMesh { - result_tx: oneshot::Sender>>, - }, - GetGossipPeerTopics { - result_tx: oneshot::Sender>>, - }, - GetGossipTopicPeers { - result_tx: oneshot::Sender>>, - }, - GetRelayMesh { - result_tx: oneshot::Sender>, - }, - /// Add a reserved peer to the peer exchange. - AddReservedPeer { - peer: PeerId, - addresses: PeerAddresses, - }, - PropagateMessage { - message_id: MessageId, - propagation_source: PeerId, - }, -} - -/// The structure is the same as `PeerResponse`, -/// but is used to prevent `PeerResponse` from being used outside the network implementation. -#[derive(Debug, Eq, PartialEq)] -pub enum AdexResponse { - Ok { response: Vec }, - None, - Err { error: String }, -} - -impl From for AdexResponse { - fn from(res: PeerResponse) -> Self { - match res { - PeerResponse::Ok { res } => AdexResponse::Ok { response: res }, - PeerResponse::None => AdexResponse::None, - PeerResponse::Err { err } => AdexResponse::Err { error: err }, - } - } -} - -impl From for PeerResponse { - fn from(res: AdexResponse) -> Self { - match res { - AdexResponse::Ok { response } => PeerResponse::Ok { res: response }, - AdexResponse::None => PeerResponse::None, - AdexResponse::Err { error } => PeerResponse::Err { err: error }, - } - } -} - -/// The structure consists of GossipsubEvent and RequestResponse events. -/// It is used to prevent the network events from being used outside the network implementation. -#[derive(Debug)] -pub enum AdexBehaviourEvent { - /// A message has been received. - /// Derived from GossipsubEvent. - Message(PeerId, MessageId, GossipsubMessage), - /// A remote subscribed to a topic. - Subscribed { - /// Remote that has subscribed. - peer_id: PeerId, - /// The topic it has subscribed to. - topic: TopicHash, - }, - /// A remote unsubscribed from a topic. - Unsubscribed { - /// Remote that has unsubscribed. - peer_id: PeerId, - /// The topic it has subscribed from. - topic: TopicHash, - }, - /// A remote peer sent a request and waits for a response. - PeerRequest { - /// Remote that sent this request. - peer_id: PeerId, - /// The serialized data. - request: Vec, - /// A channel for sending a response to this request. - /// The channel is used to identify the peer on the network that is waiting for an answer to this request. - /// See [`AdexBehaviourCmd::SendResponse`]. - response_channel: AdexResponseChannel, - }, -} - -impl From for AdexBehaviourEvent { - fn from(event: GossipsubEvent) -> Self { - match event { - GossipsubEvent::Message(peer_id, message_id, gossipsub_message) => { - AdexBehaviourEvent::Message(peer_id, message_id, gossipsub_message) - }, - GossipsubEvent::Subscribed { peer_id, topic } => AdexBehaviourEvent::Subscribed { peer_id, topic }, - GossipsubEvent::Unsubscribed { peer_id, topic } => AdexBehaviourEvent::Unsubscribed { peer_id, topic }, - } - } -} - -/// AtomicDEX libp2p Network behaviour implementation -#[derive(NetworkBehaviour)] -#[behaviour(event_process = true)] -pub struct AtomicDexBehaviour { - floodsub: Floodsub, - #[behaviour(ignore)] - event_tx: Sender, - #[behaviour(ignore)] - runtime: SwarmRuntime, - #[behaviour(ignore)] - cmd_rx: Receiver, - #[behaviour(ignore)] - netid: u16, - gossipsub: Gossipsub, - request_response: RequestResponseBehaviour, - peers_exchange: PeersExchange, - ping: AdexPing, -} - -impl AtomicDexBehaviour { - fn notify_on_adex_event(&mut self, event: AdexBehaviourEvent) { - if let Err(e) = self.event_tx.try_send(event) { - error!("notify_on_adex_event error {}", e); - } - } - - fn spawn(&self, fut: impl Future + Send + 'static) { self.runtime.spawn(fut) } - - fn process_cmd(&mut self, cmd: AdexBehaviourCmd) { - match cmd { - AdexBehaviourCmd::Subscribe { topic } => { - let topic = Topic::new(topic); - self.gossipsub.subscribe(topic); - }, - AdexBehaviourCmd::PublishMsg { topics, msg } => { - self.gossipsub.publish_many(topics.into_iter().map(Topic::new), msg); - }, - AdexBehaviourCmd::PublishMsgFrom { topics, msg, from } => { - self.gossipsub - .publish_many_from(topics.into_iter().map(Topic::new), msg, from); - }, - AdexBehaviourCmd::RequestAnyRelay { req, response_tx } => { - let relays = self.gossipsub.get_relay_mesh(); - // spawn the `request_any_peer` future - let future = request_any_peer(relays, req, self.request_response.sender(), response_tx); - self.spawn(future); - }, - AdexBehaviourCmd::RequestPeers { - req, - peers, - response_tx, - } => { - let peers = peers - .into_iter() - .filter_map(|peer| match peer.parse() { - Ok(p) => Some(p), - Err(e) => { - error!("Error on parse peer id {:?}: {:?}", peer, e); - None - }, - }) - .collect(); - let future = request_peers(peers, req, self.request_response.sender(), response_tx); - self.spawn(future); - }, - AdexBehaviourCmd::RequestRelays { req, response_tx } => { - let relays = self.gossipsub.get_relay_mesh(); - // spawn the `request_peers` future - let future = request_peers(relays, req, self.request_response.sender(), response_tx); - self.spawn(future); - }, - AdexBehaviourCmd::SendResponse { res, response_channel } => { - if let Err(response) = self.request_response.send_response(response_channel.into(), res.into()) { - error!("Error sending response: {:?}", response); - } - }, - AdexBehaviourCmd::GetPeersInfo { result_tx } => { - let result = self - .gossipsub - .get_peers_connections() - .into_iter() - .map(|(peer_id, connected_points)| { - let peer_id = peer_id.to_base58(); - let connected_points = connected_points - .into_iter() - .map(|(_conn_id, point)| match point { - ConnectedPoint::Dialer { address, .. } => address.to_string(), - ConnectedPoint::Listener { send_back_addr, .. } => send_back_addr.to_string(), - }) - .collect(); - (peer_id, connected_points) - }) - .collect(); - if result_tx.send(result).is_err() { - debug!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::GetGossipMesh { result_tx } => { - let result = self - .gossipsub - .get_mesh() - .iter() - .map(|(topic, peers)| { - let topic = topic.to_string(); - let peers = peers.iter().map(|peer| peer.to_string()).collect(); - (topic, peers) - }) - .collect(); - if result_tx.send(result).is_err() { - debug!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::GetGossipPeerTopics { result_tx } => { - let result = self - .gossipsub - .get_all_peer_topics() - .iter() - .map(|(peer, topics)| { - let peer = peer.to_string(); - let topics = topics.iter().map(|topic| topic.to_string()).collect(); - (peer, topics) - }) - .collect(); - if result_tx.send(result).is_err() { - error!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::GetGossipTopicPeers { result_tx } => { - let result = self - .gossipsub - .get_all_topic_peers() - .iter() - .map(|(topic, peers)| { - let topic = topic.to_string(); - let peers = peers.iter().map(|peer| peer.to_string()).collect(); - (topic, peers) - }) - .collect(); - if result_tx.send(result).is_err() { - error!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::GetRelayMesh { result_tx } => { - let result = self - .gossipsub - .get_relay_mesh() - .into_iter() - .map(|peer| peer.to_string()) - .collect(); - if result_tx.send(result).is_err() { - error!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::AddReservedPeer { peer, addresses } => { - self.peers_exchange - .add_peer_addresses_to_reserved_peers(&peer, addresses); - }, - AdexBehaviourCmd::PropagateMessage { - message_id, - propagation_source, - } => { - self.gossipsub.propagate_message(&message_id, &propagation_source); - }, - } - } - - fn announce_listeners(&mut self, listeners: PeerAddresses) { - let serialized = rmp_serde::to_vec(&listeners).expect("PeerAddresses serialization should never fail"); - self.floodsub.publish(FloodsubTopic::new(PEERS_TOPIC), serialized); - } - - pub fn connected_relays_len(&self) -> usize { self.gossipsub.connected_relays_len() } - - pub fn relay_mesh_len(&self) -> usize { self.gossipsub.relay_mesh_len() } - - pub fn received_messages_in_period(&self) -> (Duration, usize) { self.gossipsub.get_received_messages_in_period() } - - pub fn connected_peers_len(&self) -> usize { self.gossipsub.get_num_peers() } -} - -impl NetworkBehaviourEventProcess for AtomicDexBehaviour { - fn inject_event(&mut self, event: GossipsubEvent) { self.notify_on_adex_event(event.into()); } -} - -impl NetworkBehaviourEventProcess for AtomicDexBehaviour { - fn inject_event(&mut self, event: FloodsubEvent) { - // do not process peer announce on 8762 temporary - if self.netid != NETID_8762 { - if let FloodsubEvent::Message(message) = &event { - for topic in &message.topics { - if topic == &FloodsubTopic::new(PEERS_TOPIC) { - let addresses: PeerAddresses = match rmp_serde::from_slice(&message.data) { - Ok(a) => a, - Err(_) => return, - }; - self.peers_exchange - .add_peer_addresses_to_known_peers(&message.source, addresses); - } - } - } - } - } -} - -impl NetworkBehaviourEventProcess for AtomicDexBehaviour { - fn inject_event(&mut self, _event: Void) {} -} - -impl NetworkBehaviourEventProcess<()> for AtomicDexBehaviour { - fn inject_event(&mut self, _event: ()) {} -} - -impl NetworkBehaviourEventProcess for AtomicDexBehaviour { - fn inject_event(&mut self, event: RequestResponseBehaviourEvent) { - match event { - RequestResponseBehaviourEvent::InboundRequest { - peer_id, - request, - response_channel, - } => { - let event = AdexBehaviourEvent::PeerRequest { - peer_id, - request: request.req, - response_channel: response_channel.into(), - }; - // forward the event to the AdexBehaviourCmd handler - self.notify_on_adex_event(event); - }, - } - } -} - -/// Custom types mapping the complex associated types of AtomicDexBehaviour to the ExpandedSwarm -type AtomicDexSwarm = Swarm; - -fn maintain_connection_to_relays(swarm: &mut AtomicDexSwarm, bootstrap_addresses: &[Multiaddr]) { - let behaviour = swarm.behaviour(); - let connected_relays = behaviour.gossipsub.connected_relays(); - let mesh_n_low = behaviour.gossipsub.get_config().mesh_n_low; - let mesh_n = behaviour.gossipsub.get_config().mesh_n; - // allow 2 * mesh_n_high connections to other nodes - let max_n = behaviour.gossipsub.get_config().mesh_n_high * 2; - - let mut rng = rand::thread_rng(); - if connected_relays.len() < mesh_n_low { - let to_connect_num = mesh_n - connected_relays.len(); - let to_connect = swarm - .behaviour_mut() - .peers_exchange - .get_random_peers(to_connect_num, |peer| !connected_relays.contains(peer)); - - // choose some random bootstrap addresses to connect if peers exchange returned not enough peers - if to_connect.len() < to_connect_num { - let connect_bootstrap_num = to_connect_num - to_connect.len(); - for addr in bootstrap_addresses - .iter() - .filter(|addr| !swarm.behaviour().gossipsub.is_connected_to_addr(addr)) - .collect::>() - .choose_multiple(&mut rng, connect_bootstrap_num) - { - if let Err(e) = libp2p::Swarm::dial(swarm, (*addr).clone()) { - error!("Bootstrap addr {} dial error {}", addr, e); - } - } - } - for (peer, addresses) in to_connect { - for addr in addresses { - if swarm.behaviour().gossipsub.is_connected_to_addr(&addr) { - continue; - } - if let Err(e) = libp2p::Swarm::dial(swarm, addr.clone()) { - error!("Peer {} address {} dial error {}", peer, addr, e); - } - } - } - } - - if connected_relays.len() > max_n { - let to_disconnect_num = connected_relays.len() - max_n; - let relays_mesh = swarm.behaviour().gossipsub.get_relay_mesh(); - let not_in_mesh: Vec<_> = connected_relays - .iter() - .filter(|peer| !relays_mesh.contains(peer)) - .collect(); - for peer in not_in_mesh.choose_multiple(&mut rng, to_disconnect_num) { - if !swarm.behaviour().peers_exchange.is_reserved_peer(peer) { - info!("Disconnecting peer {}", peer); - if Swarm::disconnect_peer_id(swarm, **peer).is_err() { - error!("Peer {} disconnect error", peer); - } - } - } - } - - for relay in connected_relays { - if !swarm.behaviour().peers_exchange.is_known_peer(&relay) { - swarm.behaviour_mut().peers_exchange.add_known_peer(relay); - } - } -} - -fn announce_my_addresses(swarm: &mut AtomicDexSwarm) { - let global_listeners: PeerAddresses = Swarm::listeners(swarm) - .filter(|listener| { - for protocol in listener.iter() { - if let Protocol::Ip4(ip) = protocol { - return ip.is_global(); - } - } - false - }) - .take(1) - .cloned() - .collect(); - if !global_listeners.is_empty() { - swarm.behaviour_mut().announce_listeners(global_listeners); - } -} - -#[derive(Debug, Display)] -pub enum AdexBehaviourError { - #[display(fmt = "{}", _0)] - ParsingRelayAddress(RelayAddressError), -} - -impl From for AdexBehaviourError { - fn from(e: RelayAddressError) -> Self { AdexBehaviourError::ParsingRelayAddress(e) } -} - -pub struct WssCerts { - pub server_priv_key: rustls::PrivateKey, - pub certs: Vec, -} - -pub enum NodeType { - Light { - network_ports: NetworkPorts, - }, - LightInMemory, - Relay { - ip: IpAddr, - network_ports: NetworkPorts, - wss_certs: Option, - }, - RelayInMemory { - port: u64, - }, -} - -impl NodeType { - pub fn to_network_info(&self) -> NetworkInfo { - match self { - NodeType::Light { network_ports } | NodeType::Relay { network_ports, .. } => NetworkInfo::Distributed { - network_ports: *network_ports, - }, - NodeType::LightInMemory | NodeType::RelayInMemory { .. } => NetworkInfo::InMemory, - } - } - - pub fn is_relay(&self) -> bool { matches!(self, NodeType::Relay { .. } | NodeType::RelayInMemory { .. }) } - - pub fn wss_certs(&self) -> Option<&WssCerts> { - match self { - NodeType::Relay { wss_certs, .. } => wss_certs.as_ref(), - _ => None, - } - } -} - -/// Creates and spawns new AdexBehaviour Swarm returning: -/// 1. tx to send control commands -/// 2. rx emitting gossip events to processing side -/// 3. our peer_id -/// 4. abort handle to stop the P2P processing fut. -pub async fn spawn_gossipsub( - netid: u16, - force_key: Option<[u8; 32]>, - runtime: SwarmRuntime, - to_dial: Vec, - node_type: NodeType, - on_poll: impl Fn(&AtomicDexSwarm) + Send + 'static, -) -> Result<(Sender, AdexEventRx, PeerId), AdexBehaviourError> { - let (result_tx, result_rx) = oneshot::channel(); - - let runtime_c = runtime.clone(); - let fut = async move { - let result = start_gossipsub(netid, force_key, runtime, to_dial, node_type, on_poll); - result_tx.send(result).unwrap(); - }; - - // `Libp2p` must be spawned on the tokio runtime - runtime_c.spawn(fut); - result_rx.await.expect("Fatal error on starting gossipsub") -} - -/// Creates and spawns new AdexBehaviour Swarm returning: -/// 1. tx to send control commands -/// 2. rx emitting gossip events to processing side -/// 3. our peer_id -/// 4. abort handle to stop the P2P processing fut -/// -/// Prefer using [`spawn_gossipsub`] to make sure the Swarm is initialized and spawned on the same runtime. -/// Otherwise, you can face the following error: -/// `panicked at 'there is no reactor running, must be called from the context of a Tokio 1.x runtime'`. -#[allow(clippy::too_many_arguments)] -fn start_gossipsub( - netid: u16, - force_key: Option<[u8; 32]>, - runtime: SwarmRuntime, - to_dial: Vec, - node_type: NodeType, - on_poll: impl Fn(&AtomicDexSwarm) + Send + 'static, -) -> Result<(Sender, AdexEventRx, PeerId), AdexBehaviourError> { - let i_am_relay = node_type.is_relay(); - let mut rng = rand::thread_rng(); - let local_key = generate_ed25519_keypair(&mut rng, force_key); - let local_peer_id = PeerId::from(local_key.public()); - info!("Local peer id: {:?}", local_peer_id); - - let noise_keys = noise::Keypair::::new() - .into_authentic(&local_key) - .expect("Signing libp2p-noise static DH keypair failed."); - - let network_info = node_type.to_network_info(); - let transport = match network_info { - NetworkInfo::InMemory => build_memory_transport(noise_keys), - NetworkInfo::Distributed { .. } => build_dns_ws_transport(noise_keys, node_type.wss_certs()), - }; - - let (cmd_tx, cmd_rx) = channel(CHANNEL_BUF_SIZE); - let (event_tx, event_rx) = channel(CHANNEL_BUF_SIZE); - - let bootstrap = to_dial - .into_iter() - .map(|addr| addr.try_to_multiaddr(network_info)) - .collect::, _>>()?; - - let (mesh_n_low, mesh_n, mesh_n_high) = if i_am_relay { (4, 6, 12) } else { (2, 3, 4) }; - - // Create a Swarm to manage peers and events - let mut swarm = { - // to set default parameters for gossipsub use: - // let gossipsub_config = gossipsub::GossipsubConfig::default(); - - // To content-address message, we can take the hash of message and use it as an ID. - let message_id_fn = |message: &GossipsubMessage| { - let mut s = DefaultHasher::new(); - message.data.hash(&mut s); - message.sequence_number.hash(&mut s); - MessageId(s.finish().to_string()) - }; - - // set custom gossipsub - let gossipsub_config = GossipsubConfigBuilder::new() - .message_id_fn(message_id_fn) - .i_am_relay(i_am_relay) - .mesh_n_low(mesh_n_low) - .mesh_n(mesh_n) - .mesh_n_high(mesh_n_high) - .manual_propagation() - .max_transmit_size(1024 * 1024 - 100) - .build(); - // build a gossipsub network behaviour - let mut gossipsub = Gossipsub::new(local_peer_id, gossipsub_config); - - let floodsub = Floodsub::new(local_peer_id, netid != NETID_8762); - - let mut peers_exchange = PeersExchange::new(network_info); - if !network_info.in_memory() { - // Please note WASM nodes don't support `PeersExchange` currently, - // so `get_all_network_seednodes` returns an empty list. - for (peer_id, addr) in get_all_network_seednodes(netid) { - let multiaddr = addr.try_to_multiaddr(network_info)?; - peers_exchange.add_peer_addresses_to_known_peers(&peer_id, iter::once(multiaddr).collect()); - gossipsub.add_explicit_relay(peer_id); - } - } - - // build a request-response network behaviour - let request_response = build_request_response_behaviour(); - - // use default ping config with 15s interval, 20s timeout and 1 max failure - let ping = AdexPing::new(); - - let adex_behavior = AtomicDexBehaviour { - floodsub, - event_tx, - runtime: runtime.clone(), - cmd_rx, - netid, - gossipsub, - request_response, - peers_exchange, - ping, - }; - libp2p::swarm::SwarmBuilder::new(transport, adex_behavior, local_peer_id) - .executor(Box::new(runtime.clone())) - .build() - }; - swarm - .behaviour_mut() - .floodsub - .subscribe(FloodsubTopic::new(PEERS_TOPIC.to_owned())); - - match node_type { - NodeType::Relay { - ip, - network_ports, - wss_certs, - } => { - let dns_addr: Multiaddr = format!("/ip4/{}/tcp/{}", ip, network_ports.tcp).parse().unwrap(); - libp2p::Swarm::listen_on(&mut swarm, dns_addr).unwrap(); - if wss_certs.is_some() { - let wss_addr: Multiaddr = format!("/ip4/{}/tcp/{}/wss", ip, network_ports.wss).parse().unwrap(); - libp2p::Swarm::listen_on(&mut swarm, wss_addr).unwrap(); - } - }, - NodeType::RelayInMemory { port } => { - let memory_addr: Multiaddr = format!("/memory/{}", port).parse().unwrap(); - libp2p::Swarm::listen_on(&mut swarm, memory_addr).unwrap(); - }, - _ => (), - } - - for relay in bootstrap.choose_multiple(&mut rng, mesh_n) { - match libp2p::Swarm::dial(&mut swarm, relay.clone()) { - Ok(_) => info!("Dialed {}", relay), - Err(e) => error!("Dial {:?} failed: {:?}", relay, e), - } - } - - let mut check_connected_relays_interval = Interval::new_at( - Instant::now() + CONNECTED_RELAYS_CHECK_INTERVAL, - CONNECTED_RELAYS_CHECK_INTERVAL, - ); - let mut announce_interval = Interval::new_at(Instant::now() + ANNOUNCE_INITIAL_DELAY, ANNOUNCE_INTERVAL); - let mut listening = false; - let polling_fut = poll_fn(move |cx: &mut Context| { - loop { - match swarm.behaviour_mut().cmd_rx.poll_next_unpin(cx) { - Poll::Ready(Some(cmd)) => swarm.behaviour_mut().process_cmd(cmd), - Poll::Ready(None) => return Poll::Ready(()), - Poll::Pending => break, - } - } - - loop { - match swarm.poll_next_unpin(cx) { - Poll::Ready(Some(event)) => debug!("Swarm event {:?}", event), - Poll::Ready(None) => return Poll::Ready(()), - Poll::Pending => break, - } - } - - if swarm.behaviour().gossipsub.is_relay() { - while let Poll::Ready(Some(())) = announce_interval.poll_next_unpin(cx) { - announce_my_addresses(&mut swarm); - } - } - - while let Poll::Ready(Some(())) = check_connected_relays_interval.poll_next_unpin(cx) { - maintain_connection_to_relays(&mut swarm, &bootstrap); - } - - if !listening && i_am_relay { - for listener in Swarm::listeners(&swarm) { - info!("Listening on {}", listener); - listening = true; - } - } - on_poll(&swarm); - Poll::Pending - }); - - runtime.spawn(polling_fut.then(|_| futures::future::ready(()))); - Ok((cmd_tx, event_rx, local_peer_id)) -} - -#[cfg(target_arch = "wasm32")] -fn build_dns_ws_transport( - noise_keys: libp2p::noise::AuthenticKeypair, - _wss_certs: Option<&WssCerts>, -) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> { - let websocket = libp2p::wasm_ext::ffi::websocket_transport(); - let transport = libp2p::wasm_ext::ExtTransport::new(websocket); - upgrade_transport(transport, noise_keys) -} - -#[cfg(not(target_arch = "wasm32"))] -fn build_dns_ws_transport( - noise_keys: libp2p::noise::AuthenticKeypair, - wss_certs: Option<&WssCerts>, -) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> { - use libp2p::websocket::tls as libp2p_tls; - - let ws_tcp = libp2p::dns::TokioDnsConfig::custom( - libp2p::tcp::TokioTcpConfig::new().nodelay(true), - libp2p::dns::ResolverConfig::google(), - Default::default(), - ) - .unwrap(); - let mut ws_dns_tcp = libp2p::websocket::WsConfig::new(ws_tcp); - - if let Some(certs) = wss_certs { - let server_priv_key = libp2p_tls::PrivateKey::new(certs.server_priv_key.0.clone()); - let certs = certs - .certs - .iter() - .map(|cert| libp2p_tls::Certificate::new(cert.0.clone())); - let wss_config = libp2p_tls::Config::new(server_priv_key, certs).unwrap(); - ws_dns_tcp.set_tls_config(wss_config); - } - - // This is for preventing port reuse of dns/tcp instead of - // websocket ports. - let dns_tcp = libp2p::dns::TokioDnsConfig::custom( - libp2p::tcp::TokioTcpConfig::new().nodelay(true), - libp2p::dns::ResolverConfig::google(), - Default::default(), - ) - .unwrap(); - - let transport = dns_tcp.or_transport(ws_dns_tcp); - upgrade_transport(transport, noise_keys) -} - -fn build_memory_transport( - noise_keys: libp2p::noise::AuthenticKeypair, -) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> { - let transport = libp2p::core::transport::MemoryTransport::default(); - upgrade_transport(transport, noise_keys) -} - -/// Set up an encrypted Transport over the Mplex protocol. -fn upgrade_transport( - transport: T, - noise_keys: libp2p::noise::AuthenticKeypair, -) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> -where - T: Transport + Send + Sync + 'static, - T::Output: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static, - T::ListenerUpgrade: Send, - T::Listener: Send, - T::Dial: Send, - T::Error: Send + Sync + 'static, -{ - transport - .upgrade(libp2p::core::upgrade::Version::V1) - .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) - .multiplex(libp2p::mplex::MplexConfig::default()) - .timeout(std::time::Duration::from_secs(20)) - .map(|(peer, muxer), _| (peer, libp2p::core::muxing::StreamMuxerBox::new(muxer))) - .boxed() -} - -fn generate_ed25519_keypair(rng: &mut R, force_key: Option<[u8; 32]>) -> identity::Keypair { - let mut raw_key = match force_key { - Some(key) => key, - None => { - let mut key = [0; 32]; - rng.fill_bytes(&mut key); - key - }, - }; - let secret = identity::ed25519::SecretKey::from_bytes(&mut raw_key).expect("Secret length is 32 bytes"); - let keypair = identity::ed25519::Keypair::from(secret); - identity::Keypair::Ed25519(keypair) -} - -/// Request the peers sequential until a `PeerResponse::Ok()` will not be received. -async fn request_any_peer( - peers: Vec, - request_data: Vec, - request_response_tx: RequestResponseSender, - response_tx: oneshot::Sender)>>, -) { - debug!("start request_any_peer loop: peers {}", peers.len()); - for peer in peers { - match request_one_peer(peer, request_data.clone(), request_response_tx.clone()).await { - PeerResponse::Ok { res } => { - debug!("Received a response from peer {:?}, stop the request loop", peer); - if response_tx.send(Some((peer, res))).is_err() { - error!("Response oneshot channel was closed"); - } - return; - }, - PeerResponse::None => { - debug!("Received None from peer {:?}, request next peer", peer); - }, - PeerResponse::Err { err } => { - error!("Error on request {:?} peer: {:?}. Request next peer", peer, err); - }, - }; - } - - debug!("None of the peers responded to the request"); - if response_tx.send(None).is_err() { - error!("Response oneshot channel was closed"); - }; -} - -/// Request the peers and collect all their responses. -async fn request_peers( - peers: Vec, - request_data: Vec, - request_response_tx: RequestResponseSender, - response_tx: oneshot::Sender>, -) { - debug!("start request_any_peer loop: peers {}", peers.len()); - let mut futures = Vec::with_capacity(peers.len()); - for peer in peers { - let request_data = request_data.clone(); - let request_response_tx = request_response_tx.clone(); - futures.push(async move { - let response = request_one_peer(peer, request_data, request_response_tx).await; - (peer, response) - }) - } - - let responses = join_all(futures) - .await - .into_iter() - .map(|(peer_id, res)| { - let res: AdexResponse = res.into(); - (peer_id, res) - }) - .collect(); - - if response_tx.send(responses).is_err() { - error!("Response oneshot channel was closed"); - }; -} - -async fn request_one_peer(peer: PeerId, req: Vec, mut request_response_tx: RequestResponseSender) -> PeerResponse { - // Use the internal receiver to receive a response to this request. - let (internal_response_tx, internal_response_rx) = oneshot::channel(); - let request = PeerRequest { req }; - request_response_tx - .send((peer, request, internal_response_tx)) - .await - .unwrap(); - - match internal_response_rx.await { - Ok(response) => response, - Err(e) => PeerResponse::Err { - err: format!("Error on request the peer {:?}: \"{:?}\". Request next peer", peer, e), - }, - } -} diff --git a/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs b/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs deleted file mode 100644 index 4dfdfaa2cb..0000000000 --- a/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs +++ /dev/null @@ -1,368 +0,0 @@ -use super::{spawn_gossipsub, AdexBehaviourCmd, AdexBehaviourEvent, AdexResponse, NodeType, RelayAddress, SwarmRuntime}; -use async_std::task::spawn; -use common::executor::abortable_queue::AbortableQueue; -use futures::channel::{mpsc, oneshot}; -use futures::{SinkExt, StreamExt}; -use libp2p::PeerId; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::Arc; -#[cfg(not(windows))] use std::sync::Mutex; -use std::time::Duration; - -static TEST_LISTEN_PORT: AtomicU64 = AtomicU64::new(1); - -lazy_static! { - static ref SYSTEM: AbortableQueue = AbortableQueue::default(); -} - -fn next_port() -> u64 { TEST_LISTEN_PORT.fetch_add(1, Ordering::Relaxed) } - -struct Node { - peer_id: PeerId, - cmd_tx: mpsc::Sender, -} - -impl Node { - async fn spawn(port: u64, seednodes: Vec, on_event: F) -> Node - where - F: Fn(mpsc::Sender, AdexBehaviourEvent) + Send + 'static, - { - let spawner = SwarmRuntime::new(SYSTEM.weak_spawner()); - let node_type = NodeType::RelayInMemory { port }; - let seednodes = seednodes.into_iter().map(RelayAddress::Memory).collect(); - let (cmd_tx, mut event_rx, peer_id) = spawn_gossipsub(333, None, spawner, seednodes, node_type, |_| {}) - .await - .expect("Error spawning AdexBehaviour"); - - // spawn a response future - let cmd_tx_fut = cmd_tx.clone(); - spawn(async move { - loop { - let cmd_tx_fut = cmd_tx_fut.clone(); - match event_rx.next().await { - Some(r) => on_event(cmd_tx_fut, r), - _ => { - log!("Finish response future"); - break; - }, - } - } - }); - - Node { peer_id, cmd_tx } - } - - async fn send_cmd(&mut self, cmd: AdexBehaviourCmd) { self.cmd_tx.send(cmd).await.unwrap(); } - - async fn wait_peers(&mut self, number: usize) { - let mut attempts = 0; - loop { - let (tx, rx) = oneshot::channel(); - self.cmd_tx - .send(AdexBehaviourCmd::GetPeersInfo { result_tx: tx }) - .await - .unwrap(); - match rx.await { - Ok(map) => { - if map.len() >= number { - return; - } - async_std::task::sleep(Duration::from_millis(500)).await; - }, - Err(e) => panic!("{}", e), - } - attempts += 1; - if attempts >= 10 { - panic!("wait_peers {} attempts exceeded", attempts); - } - } - } -} - -#[tokio::test] -async fn test_request_response_ok() { - let _ = env_logger::try_init(); - - let request_received = Arc::new(AtomicBool::new(false)); - let request_received_cpy = request_received.clone(); - - let node1_port = next_port(); - let node1 = Node::spawn(node1_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - request_received_cpy.store(true, Ordering::Relaxed); - assert_eq!(request, b"test request"); - - let res = AdexResponse::Ok { - response: b"test response".to_vec(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - - let mut node2 = Node::spawn(next_port(), vec![node1_port], |_, _| ()).await; - - node2.wait_peers(1).await; - - let (response_tx, response_rx) = oneshot::channel(); - node2 - .send_cmd(AdexBehaviourCmd::RequestAnyRelay { - req: b"test request".to_vec(), - response_tx, - }) - .await; - - let response = response_rx.await.unwrap(); - assert_eq!(response, Some((node1.peer_id, b"test response".to_vec()))); - - assert!(request_received.load(Ordering::Relaxed)); -} - -#[tokio::test] -#[cfg(not(windows))] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 -async fn test_request_response_ok_three_peers() { - let _ = env_logger::try_init(); - - #[derive(Default)] - struct RequestHandler { - requests: u8, - } - - impl RequestHandler { - fn handle(&mut self, mut cmd_tx: mpsc::Sender, event: AdexBehaviourEvent) { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - self.requests += 1; - - assert_eq!(request, b"test request"); - - // the first time we should respond the none - if self.requests == 1 { - let res = AdexResponse::None; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - return; - } - - // the second time we should respond an error - if self.requests == 2 { - let res = AdexResponse::Err { - error: "test error".into(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - return; - } - - // the third time we should respond an ok - if self.requests == 3 { - let res = AdexResponse::Ok { - response: format!("success {} request", self.requests).as_bytes().to_vec(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - return; - } - - panic!("Request received more than 3 times"); - } - } - - let request_handler = Arc::new(Mutex::new(RequestHandler::default())); - - let mut receivers = Vec::new(); - for _ in 0..3 { - let handler = request_handler.clone(); - let receiver_port = next_port(); - let receiver = Node::spawn(receiver_port, vec![], move |cmd_tx, event| { - let mut handler = handler.lock().unwrap(); - handler.handle(cmd_tx, event) - }) - .await; - receivers.push((receiver_port, receiver)); - } - - let mut sender = Node::spawn( - next_port(), - receivers.iter().map(|(port, _)| *port).collect(), - |_, _| (), - ) - .await; - - sender.wait_peers(3).await; - - let (response_tx, response_rx) = oneshot::channel(); - sender - .send_cmd(AdexBehaviourCmd::RequestAnyRelay { - req: b"test request".to_vec(), - response_tx, - }) - .await; - - let (_peer_id, res) = response_rx.await.unwrap().unwrap(); - assert_eq!(res, b"success 3 request".to_vec()); -} - -#[tokio::test] -async fn test_request_response_none() { - let _ = env_logger::try_init(); - - let request_received = Arc::new(AtomicBool::new(false)); - let request_received_cpy = request_received.clone(); - - let node1_port = next_port(); - let _node1 = Node::spawn(node1_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - request_received_cpy.store(true, Ordering::Relaxed); - assert_eq!(request, b"test request"); - - let res = AdexResponse::None; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - - let mut node2 = Node::spawn(next_port(), vec![node1_port], |_, _| ()).await; - - node2.wait_peers(1).await; - - let (response_tx, response_rx) = oneshot::channel(); - node2 - .send_cmd(AdexBehaviourCmd::RequestAnyRelay { - req: b"test request".to_vec(), - response_tx, - }) - .await; - - assert_eq!(response_rx.await.unwrap(), None); - assert!(request_received.load(Ordering::Relaxed)); -} - -#[tokio::test] -#[cfg(target_os = "linux")] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 -async fn test_request_peers_ok_three_peers() { - let _ = env_logger::try_init(); - - let receiver1_port = next_port(); - let receiver1 = Node::spawn(receiver1_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - assert_eq!(request, b"test request"); - - let res = AdexResponse::None; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - - let receiver2_port = next_port(); - let receiver2 = Node::spawn(receiver2_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - assert_eq!(request, b"test request"); - - let res = AdexResponse::Err { - error: "test error".into(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - - let receiver3_port = next_port(); - let receiver3 = Node::spawn(receiver3_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - assert_eq!(request, b"test request"); - - let res = AdexResponse::Ok { - response: b"test response".to_vec(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - let mut sender = Node::spawn( - next_port(), - vec![receiver1_port, receiver2_port, receiver3_port], - |_, _| (), - ) - .await; - - sender.wait_peers(3).await; - - let (response_tx, response_rx) = oneshot::channel(); - sender - .send_cmd(AdexBehaviourCmd::RequestRelays { - req: b"test request".to_vec(), - response_tx, - }) - .await; - - let mut expected = vec![ - (receiver1.peer_id, AdexResponse::None), - (receiver2.peer_id, AdexResponse::Err { - error: "test error".into(), - }), - (receiver3.peer_id, AdexResponse::Ok { - response: b"test response".to_vec(), - }), - ]; - expected.sort_by(|x, y| x.0.cmp(&y.0)); - - let mut responses = response_rx.await.unwrap(); - responses.sort_by(|x, y| x.0.cmp(&y.0)); - assert_eq!(responses, expected); -} diff --git a/mm2src/mm2_libp2p/src/lib.rs b/mm2src/mm2_libp2p/src/lib.rs deleted file mode 100644 index ae554ca311..0000000000 --- a/mm2src/mm2_libp2p/src/lib.rs +++ /dev/null @@ -1,177 +0,0 @@ -#![feature(ip)] - -#[macro_use] extern crate lazy_static; - -mod adex_ping; -pub mod atomicdex_behaviour; -mod network; -pub mod peers_exchange; -pub mod relay_address; -pub mod request_response; -mod runtime; - -use lazy_static::lazy_static; -use secp256k1::{Message as SecpMessage, PublicKey as Secp256k1Pubkey, Secp256k1, SecretKey, SignOnly, Signature, - VerifyOnly}; -use sha2::{Digest, Sha256}; - -pub use atomicdex_behaviour::{spawn_gossipsub, AdexBehaviourError, NodeType, WssCerts}; -pub use atomicdex_gossipsub::{GossipsubEvent, GossipsubMessage, MessageId, TopicHash}; -pub use libp2p::identity::error::DecodingError; -pub use libp2p::identity::secp256k1::PublicKey as Libp2pSecpPublic; -pub use libp2p::identity::PublicKey as Libp2pPublic; -pub use libp2p::{Multiaddr, PeerId}; -pub use peers_exchange::PeerAddresses; -pub use relay_address::{RelayAddress, RelayAddressError}; -pub use runtime::SwarmRuntime; -use serde::{de, Deserialize, Serialize, Serializer}; - -lazy_static! { - static ref SECP_VERIFY: Secp256k1 = Secp256k1::verification_only(); - static ref SECP_SIGN: Secp256k1 = Secp256k1::signing_only(); -} - -#[derive(Clone, Copy)] -pub enum NetworkInfo { - /// The in-memory network. - InMemory, - /// The distributed network (out of the app memory). - Distributed { network_ports: NetworkPorts }, -} - -impl NetworkInfo { - pub fn in_memory(&self) -> bool { matches!(self, NetworkInfo::InMemory) } -} - -#[derive(Clone, Copy)] -pub struct NetworkPorts { - pub tcp: u16, - pub wss: u16, -} - -pub fn encode_message(message: &T) -> Result, rmp_serde::encode::Error> { - rmp_serde::to_vec(message) -} - -#[inline] -pub fn decode_message<'de, T: de::Deserialize<'de>>(bytes: &'de [u8]) -> Result { - rmp_serde::from_slice(bytes) -} - -#[derive(Deserialize, Serialize)] -struct SignedMessageSerdeHelper<'a> { - pubkey: PublicKey, - #[serde(with = "serde_bytes")] - signature: &'a [u8], - #[serde(with = "serde_bytes")] - payload: &'a [u8], -} - -pub fn encode_and_sign(message: &T, secret: &[u8; 32]) -> Result, rmp_serde::encode::Error> { - let secret = SecretKey::from_slice(secret).unwrap(); - let encoded = encode_message(message)?; - let sig_hash = SecpMessage::from_slice(&sha256(&encoded)).expect("Message::from_slice should never fail"); - let sig = SECP_SIGN.sign(&sig_hash, &secret); - let serialized_sig = sig.serialize_compact(); - let pubkey = PublicKey::from(Secp256k1Pubkey::from_secret_key(&*SECP_SIGN, &secret)); - let msg = SignedMessageSerdeHelper { - pubkey, - signature: &serialized_sig, - payload: &encoded, - }; - encode_message(&msg) -} - -pub fn decode_signed<'de, T: de::Deserialize<'de>>( - encoded: &'de [u8], -) -> Result<(T, Signature, PublicKey), rmp_serde::decode::Error> { - let helper: SignedMessageSerdeHelper = decode_message(encoded)?; - let signature = Signature::from_compact(helper.signature) - .map_err(|e| rmp_serde::decode::Error::Syntax(format!("Failed to parse signature {}", e)))?; - let sig_hash = SecpMessage::from_slice(&sha256(helper.payload)).expect("Message::from_slice should never fail"); - match &helper.pubkey { - PublicKey::Secp256k1(serialized_pub) => { - if SECP_VERIFY.verify(&sig_hash, &signature, &serialized_pub.0).is_err() { - return Err(rmp_serde::decode::Error::Syntax("Invalid message signature".into())); - } - }, - } - - let payload: T = decode_message(helper.payload)?; - Ok((payload, signature, helper.pubkey)) -} - -fn sha256(input: impl AsRef<[u8]>) -> [u8; 32] { Sha256::new().chain(input).finalize().into() } - -#[derive(Debug, Eq, PartialEq)] -pub struct Secp256k1PubkeySerialize(Secp256k1Pubkey); - -impl Serialize for Secp256k1PubkeySerialize { - fn serialize(&self, serializer: S) -> Result { - serializer.serialize_bytes(&self.0.serialize()) - } -} - -impl<'de> de::Deserialize<'de> for Secp256k1PubkeySerialize { - fn deserialize(deserializer: D) -> Result>::Error> - where - D: de::Deserializer<'de>, - { - let slice: &[u8] = de::Deserialize::deserialize(deserializer)?; - let pubkey = - Secp256k1Pubkey::from_slice(slice).map_err(|e| de::Error::custom(format!("Error {} parsing pubkey", e)))?; - - Ok(Secp256k1PubkeySerialize(pubkey)) - } -} - -#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] -pub enum PublicKey { - Secp256k1(Secp256k1PubkeySerialize), -} - -impl PublicKey { - pub fn to_bytes(&self) -> Vec { - match self { - PublicKey::Secp256k1(pubkey) => pubkey.0.serialize().to_vec(), - } - } - - pub fn to_hex(&self) -> String { - match self { - PublicKey::Secp256k1(pubkey) => hex::encode(pubkey.0.serialize().as_ref()), - } - } - - pub fn unprefixed(&self) -> [u8; 32] { - let mut res = [0; 32]; - match self { - PublicKey::Secp256k1(pubkey) => res.copy_from_slice(&pubkey.0.serialize()[1..33]), - } - res - } -} - -impl From for PublicKey { - fn from(pubkey: Secp256k1Pubkey) -> Self { PublicKey::Secp256k1(Secp256k1PubkeySerialize(pubkey)) } -} - -pub type TopicPrefix = &'static str; -pub const TOPIC_SEPARATOR: char = '/'; - -pub fn pub_sub_topic(prefix: TopicPrefix, topic: &str) -> String { - let mut res = prefix.to_owned(); - res.push(TOPIC_SEPARATOR); - res.push_str(topic); - res -} - -#[test] -fn signed_message_serde() { - let secret = [1u8; 32]; - let initial_msg = vec![0u8; 32]; - let signed_encoded = encode_and_sign(&initial_msg, &secret).unwrap(); - - let (decoded, ..) = decode_signed::>(&signed_encoded).unwrap(); - assert_eq!(decoded, initial_msg); -} diff --git a/mm2src/mm2_libp2p/src/network.rs b/mm2src/mm2_libp2p/src/network.rs deleted file mode 100644 index 0b09c444db..0000000000 --- a/mm2src/mm2_libp2p/src/network.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::RelayAddress; -use libp2p::PeerId; - -pub const NETID_8762: u16 = 8762; - -#[cfg_attr(target_arch = "wasm32", allow(dead_code))] -const ALL_NETID_8762_SEEDNODES: &[(&str, &str)] = &[ - ( - "12D3KooWHKkHiNhZtKceQehHhPqwU5W1jXpoVBgS1qst899GjvTm", - "168.119.236.251", - ), - ( - "12D3KooWAToxtunEBWCoAHjefSv74Nsmxranw8juy3eKEdrQyGRF", - "168.119.236.240", - ), - ( - "12D3KooWSmEi8ypaVzFA1AGde2RjxNW5Pvxw3qa2fVe48PjNs63R", - "168.119.236.239", - ), - ( - "12D3KooWJWBnkVsVNjiqUEPjLyHpiSmQVAJ5t6qt1Txv5ctJi9Xd", - "135.181.34.220", - ), - ( - "12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P", - "168.119.236.241", - ), - ( - "12D3KooWHBeCnJdzNk51G4mLnao9cDsjuqiMTEo5wMFXrd25bd1F", - "168.119.236.243", - ), - ( - "12D3KooWKxavLCJVrQ5Gk1kd9m6cohctGQBmiKPS9XQFoXEoyGmS", - "168.119.236.249", - ), - ( - "12D3KooW9soGyPfX6kcyh3uVXNHq1y2dPmQNt2veKgdLXkBiCVKq", - "168.119.236.246", - ), - ( - "12D3KooWL6yrrNACb7t7RPyTEPxKmq8jtrcbkcNd6H5G2hK7bXaL", - "168.119.236.233", - ), - ( - "12D3KooWMrjLmrv8hNgAoVf1RfumfjyPStzd4nv5XL47zN4ZKisb", - "168.119.237.8", - ), - ( - "12D3KooWPR2RoPi19vQtLugjCdvVmCcGLP2iXAzbDfP3tp81ZL4d", - "168.119.237.13", - ), - ( - "12D3KooWJDoV9vJdy6PnzwVETZ3fWGMhV41VhSbocR1h2geFqq9Y", - "65.108.90.210", - ), - ( - "12D3KooWEaZpH61H4yuQkaNG5AsyGdpBhKRppaLdAY52a774ab5u", - "46.4.78.11", - ), - ( - "12D3KooWAd5gPXwX7eDvKWwkr2FZGfoJceKDCA53SHmTFFVkrN7Q", - "46.4.87.18", - ), -]; - -#[cfg(target_arch = "wasm32")] -pub fn get_all_network_seednodes(_netid: u16) -> Vec<(PeerId, RelayAddress)> { Vec::new() } - -#[cfg(not(target_arch = "wasm32"))] -pub fn get_all_network_seednodes(netid: u16) -> Vec<(PeerId, RelayAddress)> { - use std::str::FromStr; - - if netid != NETID_8762 { - return Vec::new(); - } - ALL_NETID_8762_SEEDNODES - .iter() - .map(|(peer_id, ipv4)| { - let peer_id = PeerId::from_str(peer_id).expect("valid peer id"); - let address = RelayAddress::IPv4(ipv4.to_string()); - (peer_id, address) - }) - .collect() -} diff --git a/mm2src/mm2_libp2p/src/peers_exchange.rs b/mm2src/mm2_libp2p/src/peers_exchange.rs deleted file mode 100644 index 1721f73f8d..0000000000 --- a/mm2src/mm2_libp2p/src/peers_exchange.rs +++ /dev/null @@ -1,436 +0,0 @@ -use crate::request_response::Codec; -use crate::NetworkInfo; -use futures::StreamExt; -use libp2p::swarm::NetworkBehaviour; -use libp2p::{multiaddr::{Multiaddr, Protocol}, - request_response::{ProtocolName, ProtocolSupport, RequestResponse, RequestResponseConfig, - RequestResponseEvent, RequestResponseMessage}, - swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}, - NetworkBehaviour, PeerId}; -use log::{error, info, warn}; -use rand::seq::SliceRandom; -use serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize}; -use std::collections::HashSet; -use std::{collections::{HashMap, VecDeque}, - iter, - task::{Context, Poll}, - time::Duration}; -use wasm_timer::{Instant, Interval}; - -pub type PeerAddresses = HashSet; - -#[derive(Debug, Clone)] -pub enum PeersExchangeProtocol { - Version1, -} - -impl ProtocolName for PeersExchangeProtocol { - fn protocol_name(&self) -> &[u8] { - match self { - PeersExchangeProtocol::Version1 => b"/peers-exchange/1", - } - } -} - -type PeersExchangeCodec = Codec; - -const DEFAULT_PEERS_NUM: usize = 20; -const REQUEST_PEERS_INITIAL_DELAY: u64 = 20; -const REQUEST_PEERS_INTERVAL: u64 = 300; -const MAX_PEERS: usize = 100; - -#[derive(Debug, Clone, Eq, Hash, PartialEq)] -pub struct PeerIdSerde(PeerId); - -impl From for PeerIdSerde { - fn from(peer_id: PeerId) -> PeerIdSerde { PeerIdSerde(peer_id) } -} - -impl Serialize for PeerIdSerde { - fn serialize(&self, serializer: S) -> Result { - self.0.clone().to_bytes().serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for PeerIdSerde { - fn deserialize>(deserializer: D) -> Result { - let bytes: Vec = Deserialize::deserialize(deserializer)?; - let peer_id = PeerId::from_bytes(&bytes).map_err(|_| serde::de::Error::custom("PeerId::from_bytes error"))?; - Ok(PeerIdSerde(peer_id)) - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum PeersExchangeRequest { - GetKnownPeers { num: usize }, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum PeersExchangeResponse { - KnownPeers { peers: HashMap }, -} - -/// Behaviour that requests known peers list from other peers at random -#[derive(NetworkBehaviour)] -#[behaviour(poll_method = "poll", event_process = true)] -pub struct PeersExchange { - request_response: RequestResponse, - #[behaviour(ignore)] - known_peers: Vec, - #[behaviour(ignore)] - reserved_peers: Vec, - #[behaviour(ignore)] - events: VecDeque::ConnectionHandler>>, - #[behaviour(ignore)] - maintain_peers_interval: Interval, - #[behaviour(ignore)] - network_info: NetworkInfo, -} - -#[allow(clippy::new_without_default)] -impl PeersExchange { - pub fn new(network_info: NetworkInfo) -> Self { - let codec = Codec::default(); - let protocol = iter::once((PeersExchangeProtocol::Version1, ProtocolSupport::Full)); - let config = RequestResponseConfig::default(); - let request_response = RequestResponse::new(codec, protocol, config); - PeersExchange { - request_response, - known_peers: Vec::new(), - reserved_peers: Vec::new(), - events: VecDeque::new(), - maintain_peers_interval: Interval::new_at( - Instant::now() + Duration::from_secs(REQUEST_PEERS_INITIAL_DELAY), - Duration::from_secs(REQUEST_PEERS_INTERVAL), - ), - network_info, - } - } - - fn get_random_known_peers(&mut self, num: usize) -> HashMap { - let mut result = HashMap::with_capacity(num); - let mut rng = rand::thread_rng(); - let peer_ids = self - .known_peers - .clone() - .into_iter() - .filter(|peer| !self.request_response.addresses_of_peer(peer).is_empty()) - .collect::>(); - - let peer_ids = peer_ids.choose_multiple(&mut rng, num); - for peer_id in peer_ids { - let addresses = self.request_response.addresses_of_peer(peer_id).into_iter().collect(); - result.insert((*peer_id).into(), addresses); - } - result - } - - fn forget_peer(&mut self, peer: &PeerId) { - self.known_peers.retain(|known_peer| known_peer != peer); - self.forget_peer_addresses(peer); - } - - fn forget_peer_addresses(&mut self, peer: &PeerId) { - for address in self.request_response.addresses_of_peer(peer) { - if !self.is_reserved_peer(peer) { - self.request_response.remove_address(peer, &address); - } - } - } - - pub fn add_peer_addresses_to_known_peers(&mut self, peer: &PeerId, addresses: PeerAddresses) { - for address in addresses.iter() { - if !self.validate_global_multiaddr(address) { - warn!("Attempt adding a not valid address of the peer '{}': {}", peer, address); - return; - } - } - if !self.known_peers.contains(peer) && !addresses.is_empty() { - self.known_peers.push(*peer); - } - let already_known = self.request_response.addresses_of_peer(peer); - for address in addresses { - if !already_known.contains(&address) { - self.request_response.add_address(peer, address); - } - } - } - - pub fn add_peer_addresses_to_reserved_peers(&mut self, peer: &PeerId, addresses: PeerAddresses) { - for address in addresses.iter() { - if !self.validate_global_multiaddr(address) { - return; - } - } - - if !self.reserved_peers.contains(peer) && !addresses.is_empty() { - self.reserved_peers.push(*peer); - } - - let already_reserved = self.request_response.addresses_of_peer(peer); - for address in addresses { - if !already_reserved.contains(&address) { - self.request_response.add_address(peer, address); - } - } - } - - fn maintain_known_peers(&mut self) { - if self.known_peers.len() > MAX_PEERS { - let mut rng = rand::thread_rng(); - let to_remove_num = self.known_peers.len() - MAX_PEERS; - self.known_peers.shuffle(&mut rng); - let removed_peers: Vec<_> = self.known_peers.drain(..to_remove_num).collect(); - for peer in removed_peers { - self.forget_peer_addresses(&peer); - } - } - self.request_known_peers_from_random_peer(); - } - - fn request_known_peers_from_random_peer(&mut self) { - let mut rng = rand::thread_rng(); - if let Some(from_peer) = self.known_peers.choose(&mut rng) { - info!("Try to request {} peers from peer {}", DEFAULT_PEERS_NUM, from_peer); - let request = PeersExchangeRequest::GetKnownPeers { num: DEFAULT_PEERS_NUM }; - self.request_response.send_request(from_peer, request); - } - } - - pub fn get_random_peers( - &mut self, - num: usize, - mut filter: impl FnMut(&PeerId) -> bool, - ) -> HashMap { - let mut result = HashMap::with_capacity(num); - let mut rng = rand::thread_rng(); - let peer_ids = self.known_peers.iter().filter(|peer| filter(peer)).collect::>(); - for peer_id in peer_ids.choose_multiple(&mut rng, num) { - let addresses = self.request_response.addresses_of_peer(peer_id).into_iter().collect(); - result.insert(**peer_id, addresses); - } - result - } - - pub fn is_known_peer(&self, peer: &PeerId) -> bool { self.known_peers.contains(peer) } - - pub fn is_reserved_peer(&self, peer: &PeerId) -> bool { self.reserved_peers.contains(peer) } - - pub fn add_known_peer(&mut self, peer: PeerId) { - if !self.is_known_peer(&peer) { - self.known_peers.push(peer) - } - } - - fn validate_global_multiaddr(&self, address: &Multiaddr) -> bool { - let network_ports = match self.network_info { - NetworkInfo::Distributed { network_ports } => network_ports, - NetworkInfo::InMemory => panic!("PeersExchange must not be used with in-memory network"), - }; - - let mut components = address.iter(); - match components.next() { - Some(Protocol::Ip4(addr)) => { - if !addr.is_global() { - return false; - } - }, - _ => return false, - } - - match components.next() { - Some(Protocol::Tcp(port)) => { - // currently, `NetworkPorts::ws` is not supported by `PeersExchange` - if port != network_ports.tcp { - return false; - } - }, - _ => return false, - } - - true - } - - fn validate_get_known_peers_response(&self, response: &HashMap) -> bool { - if response.is_empty() { - return false; - } - - if response.len() > DEFAULT_PEERS_NUM { - return false; - } - - for addresses in response.values() { - if addresses.is_empty() { - return false; - } - - for address in addresses { - if !self.validate_global_multiaddr(address) { - warn!("Received a not valid address: {}", address); - return false; - } - } - } - true - } - - fn poll( - &mut self, - cx: &mut Context, - _params: &mut impl PollParameters, - ) -> Poll::ConnectionHandler>> { - while let Poll::Ready(Some(())) = self.maintain_peers_interval.poll_next_unpin(cx) { - self.maintain_known_peers(); - } - - if let Some(event) = self.events.pop_front() { - return Poll::Ready(event); - } - - Poll::Pending - } -} - -impl NetworkBehaviourEventProcess> for PeersExchange { - fn inject_event(&mut self, event: RequestResponseEvent) { - match event { - RequestResponseEvent::Message { message, peer } => match message { - RequestResponseMessage::Request { request, channel, .. } => match request { - PeersExchangeRequest::GetKnownPeers { num } => { - // Should not send a response in such case - if num > DEFAULT_PEERS_NUM { - return; - } - let response = PeersExchangeResponse::KnownPeers { - peers: self.get_random_known_peers(num), - }; - if let Err(_response) = self.request_response.send_response(channel, response) { - warn!("Response channel has been closed already"); - } - }, - }, - RequestResponseMessage::Response { response, .. } => match response { - PeersExchangeResponse::KnownPeers { peers } => { - info!("Got peers {:?}", peers); - - if !self.validate_get_known_peers_response(&peers) { - // if peer provides invalid response forget it and try to request from other peer - self.forget_peer(&peer); - self.request_known_peers_from_random_peer(); - return; - } - - peers.into_iter().for_each(|(peer, addresses)| { - self.add_peer_addresses_to_known_peers(&peer.0, addresses); - }); - }, - }, - }, - RequestResponseEvent::OutboundFailure { - peer, - request_id, - error, - } => { - error!( - "Outbound failure {:?} while requesting {:?} to peer {}", - error, request_id, peer - ); - self.forget_peer(&peer); - self.request_known_peers_from_random_peer(); - }, - RequestResponseEvent::InboundFailure { peer, error, .. } => { - error!( - "Inbound failure {:?} while processing request from peer {}", - error, peer - ); - }, - RequestResponseEvent::ResponseSent { .. } => (), - } - } -} - -#[cfg(test)] -mod tests { - use super::{NetworkInfo, PeerIdSerde, PeersExchange}; - use crate::{NetworkPorts, PeerId}; - use libp2p::core::Multiaddr; - use std::collections::{HashMap, HashSet}; - use std::iter::FromIterator; - - #[test] - fn test_peer_id_serde() { - let peer_id = PeerIdSerde(PeerId::random()); - let serialized = rmp_serde::to_vec(&peer_id).unwrap(); - let deserialized: PeerIdSerde = rmp_serde::from_slice(&serialized).unwrap(); - assert_eq!(peer_id.0, deserialized.0); - } - - #[test] - fn test_validate_get_known_peers_response() { - let network_info = NetworkInfo::Distributed { - network_ports: NetworkPorts { tcp: 3000, wss: 3010 }, - }; - let behaviour = PeersExchange::new(network_info); - let response = HashMap::default(); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::new())]); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = "/ip4/127.0.0.1/tcp/3000".parse().unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = "/ip4/216.58.210.142/tcp/3000".parse().unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = "/ip4/216.58.210.142/tcp/3001".parse().unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = "/ip4/216.58.210.142".parse().unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = - "/ip4/168.119.236.241/tcp/3000/p2p/12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P" - .parse() - .unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(behaviour.validate_get_known_peers_response(&response)); - - let address1: Multiaddr = - "/ip4/168.119.236.241/tcp/3000/p2p/12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P" - .parse() - .unwrap(); - - let address2: Multiaddr = "/ip4/168.119.236.241/tcp/3000".parse().unwrap(); - let response = HashMap::from_iter(vec![( - PeerIdSerde(PeerId::random()), - HashSet::from_iter(vec![address1, address2]), - )]); - assert!(behaviour.validate_get_known_peers_response(&response)); - } - - #[test] - fn test_get_random_known_peers() { - let mut behaviour = PeersExchange::new(NetworkInfo::InMemory); - let peer_id = PeerId::random(); - behaviour.add_known_peer(peer_id); - - let result = behaviour.get_random_known_peers(1); - assert!(result.is_empty()); - - let address: Multiaddr = "/ip4/168.119.236.241/tcp/3000".parse().unwrap(); - behaviour.request_response.add_address(&peer_id, address.clone()); - - let result = behaviour.get_random_known_peers(1); - assert_eq!(result.len(), 1); - - let addresses = result.get(&peer_id.into()).unwrap(); - assert_eq!(addresses.len(), 1); - assert!(addresses.contains(&address)); - } -} diff --git a/mm2src/mm2_libp2p/src/relay_address.rs b/mm2src/mm2_libp2p/src/relay_address.rs deleted file mode 100644 index d23c419632..0000000000 --- a/mm2src/mm2_libp2p/src/relay_address.rs +++ /dev/null @@ -1,186 +0,0 @@ -use crate::{NetworkInfo, NetworkPorts}; -use derive_more::Display; -use libp2p::Multiaddr; -use serde::{de, Deserialize, Deserializer, Serialize}; -use std::str::FromStr; - -#[derive(Clone, Debug, Display, Serialize)] -pub enum RelayAddressError { - #[display( - fmt = "Error parsing 'RelayAddress' from {}: address has unknown protocol, expected either IPv4 or DNS or Memory address", - found - )] - FromStrError { found: String }, - #[display( - fmt = "Error converting '{:?}' to Multiaddr: unexpected IPv4/DNS address on a memory network", - self_str - )] - DistributedAddrOnMemoryNetwork { self_str: String }, - #[display( - fmt = "Error converting '{:?}' to Multiaddr: unexpected memory address on a distributed network", - self_str - )] - MemoryAddrOnDistributedNetwork { self_str: String }, -} - -impl std::error::Error for RelayAddressError {} - -impl RelayAddressError { - fn distributed_addr_on_memory_network(addr: &RelayAddress) -> RelayAddressError { - RelayAddressError::DistributedAddrOnMemoryNetwork { - self_str: format!("{:?}", addr), - } - } - - fn memory_addr_on_distributed_network(addr: &RelayAddress) -> RelayAddressError { - RelayAddressError::MemoryAddrOnDistributedNetwork { - self_str: format!("{:?}", addr), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum RelayAddress { - IPv4(String), - Dns(String), - Memory(u64), -} - -impl FromStr for RelayAddress { - type Err = RelayAddressError; - - fn from_str(s: &str) -> Result { - // check if the string is IPv4 - if std::net::Ipv4Addr::from_str(s).is_ok() { - return Ok(RelayAddress::IPv4(s.to_string())); - } - // check if the string is a domain name - if validate_domain_name(s) { - return Ok(RelayAddress::Dns(s.to_owned())); - } - // check if the string is a `/memory/` address - if let Some(port_str) = s.strip_prefix("/memory/") { - if let Ok(port) = port_str.parse() { - return Ok(RelayAddress::Memory(port)); - } - } - Err(RelayAddressError::FromStrError { found: s.to_owned() }) - } -} - -impl<'de> Deserialize<'de> for RelayAddress { - fn deserialize(deserializer: D) -> Result>::Error> - where - D: Deserializer<'de>, - { - let addr_str = String::deserialize(deserializer)?; - RelayAddress::from_str(&addr_str).map_err(de::Error::custom) - } -} - -impl RelayAddress { - /// Try to convert `RelayAddress` to `Multiaddr` using the given `network_info`. - pub fn try_to_multiaddr(&self, network_info: NetworkInfo) -> Result { - let network_ports = match network_info { - NetworkInfo::InMemory => match self { - RelayAddress::Memory(port) => return Ok(memory_multiaddr(*port)), - _ => return Err(RelayAddressError::distributed_addr_on_memory_network(self)), - }, - NetworkInfo::Distributed { network_ports } => network_ports, - }; - - match self { - RelayAddress::IPv4(ipv4) => Ok(ipv4_multiaddr(ipv4, network_ports)), - RelayAddress::Dns(dns) => Ok(dns_multiaddr(dns, network_ports)), - RelayAddress::Memory(_) => Err(RelayAddressError::memory_addr_on_distributed_network(self)), - } - } -} - -/// Use [this](https://regex101.com/r/94nCB5/1) regular expression to validate the domain name. -/// See examples at the linked resource above. -fn validate_domain_name(s: &str) -> bool { - use regex::Regex; - - lazy_static! { - static ref DNS_REGEX: Regex = Regex::new(r#"^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$"#).unwrap(); - } - - DNS_REGEX.is_match(s) -} - -fn memory_multiaddr(port: u64) -> Multiaddr { format!("/memory/{}", port).parse().unwrap() } - -#[cfg(target_arch = "wasm32")] -fn ipv4_multiaddr(ipv4_addr: &str, ports: NetworkPorts) -> Multiaddr { - format!("/ip4/{}/tcp/{}/wss", ipv4_addr, ports.wss).parse().unwrap() -} - -#[cfg(not(target_arch = "wasm32"))] -fn ipv4_multiaddr(ipv4_addr: &str, ports: NetworkPorts) -> Multiaddr { - format!("/ip4/{}/tcp/{}", ipv4_addr, ports.tcp).parse().unwrap() -} - -#[cfg(target_arch = "wasm32")] -fn dns_multiaddr(dns_addr: &str, ports: NetworkPorts) -> Multiaddr { - format!("/dns/{}/tcp/{}/wss", dns_addr, ports.wss).parse().unwrap() -} - -#[cfg(not(target_arch = "wasm32"))] -fn dns_multiaddr(dns_addr: &str, ports: NetworkPorts) -> Multiaddr { - format!("/dns/{}/tcp/{}", dns_addr, ports.tcp).parse().unwrap() -} - -#[test] -fn test_relay_address_from_str() { - let valid_addresses = vec![ - ("127.0.0.1", RelayAddress::IPv4("127.0.0.1".to_owned())), - ("255.255.255.255", RelayAddress::IPv4("255.255.255.255".to_owned())), - ("google.com", RelayAddress::Dns("google.com".to_owned())), - ("www.google.com", RelayAddress::Dns("www.google.com".to_owned())), - ("g.co", RelayAddress::Dns("g.co".to_owned())), - ( - "stackoverflow.co.uk", - RelayAddress::Dns("stackoverflow.co.uk".to_owned()), - ), - ("1.2.3.4.com", RelayAddress::Dns("1.2.3.4.com".to_owned())), - ("/memory/123", RelayAddress::Memory(123)), - ("/memory/71428421981", RelayAddress::Memory(71428421981)), - ]; - for (s, expected) in valid_addresses { - let actual = RelayAddress::from_str(s).unwrap_or_else(|_| panic!("Error parsing '{}'", s)); - assert_eq!(actual, expected); - } - - let invalid_addresses = vec![ - "127.0.0", - "127.0.0.0.2", - "google.c", - "http://google.com", - "https://google.com/", - "google.com/", - "/memory/", - "/memory/9999999999999999999999999999999", - ]; - for s in invalid_addresses { - let _ = RelayAddress::from_str(s).expect_err("Expected an error"); - } -} - -#[test] -fn test_deserialize_relay_address() { - #[derive(Deserialize, PartialEq)] - struct Config { - addresses: Vec, - } - - let Config { addresses: actual } = - serde_json::from_str(r#"{"addresses": ["foo.bar.com", "127.0.0.2", "/memory/12345"]}"#) - .expect("Error deserializing a list of RelayAddress"); - let expected = vec![ - RelayAddress::Dns("foo.bar.com".to_owned()), - RelayAddress::IPv4("127.0.0.2".to_owned()), - RelayAddress::Memory(12345), - ]; - assert_eq!(actual, expected); -} diff --git a/mm2src/mm2_libp2p/src/request_response.rs b/mm2src/mm2_libp2p/src/request_response.rs deleted file mode 100644 index 25b87e46de..0000000000 --- a/mm2src/mm2_libp2p/src/request_response.rs +++ /dev/null @@ -1,320 +0,0 @@ -use crate::{decode_message, encode_message}; -use async_trait::async_trait; -use core::iter; -use futures::channel::{mpsc, oneshot}; -use futures::io::{AsyncRead, AsyncWrite}; -use futures::task::{Context, Poll}; -use futures::StreamExt; -use libp2p::core::upgrade::{read_length_prefixed, write_length_prefixed}; -use libp2p::request_response::{ProtocolName, ProtocolSupport, RequestId, RequestResponse, RequestResponseCodec, - RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ResponseChannel}; -use libp2p::swarm::{NetworkBehaviour, NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}; -use libp2p::NetworkBehaviour; -use libp2p::PeerId; -use log::{debug, error, warn}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, VecDeque}; -use std::io; -use std::time::Duration; -use wasm_timer::{Instant, Interval}; - -const MAX_BUFFER_SIZE: usize = 1024 * 1024 - 100; - -pub type RequestResponseReceiver = mpsc::UnboundedReceiver<(PeerId, PeerRequest, oneshot::Sender)>; -pub type RequestResponseSender = mpsc::UnboundedSender<(PeerId, PeerRequest, oneshot::Sender)>; - -/// Build a request-response network behaviour. -pub fn build_request_response_behaviour() -> RequestResponseBehaviour { - let config = RequestResponseConfig::default(); - let protocol = iter::once((Protocol::Version1, ProtocolSupport::Full)); - let inner = RequestResponse::new(Codec::default(), protocol, config); - - let (tx, rx) = mpsc::unbounded(); - let pending_requests = HashMap::new(); - let events = VecDeque::new(); - let timeout = Duration::from_secs(10); - let timeout_interval = Interval::new(Duration::from_secs(1)); - - RequestResponseBehaviour { - inner, - rx, - tx, - pending_requests, - events, - timeout, - timeout_interval, - } -} - -pub enum RequestResponseBehaviourEvent { - InboundRequest { - peer_id: PeerId, - request: PeerRequest, - response_channel: ResponseChannel, - }, -} - -struct PendingRequest { - tx: oneshot::Sender, - initiated_at: Instant, -} - -#[derive(NetworkBehaviour)] -#[behaviour(out_event = "RequestResponseBehaviourEvent", event_process = true)] -#[behaviour(poll_method = "poll_event")] -pub struct RequestResponseBehaviour { - /// The inner RequestResponse network behaviour. - inner: RequestResponse>, - #[behaviour(ignore)] - rx: RequestResponseReceiver, - #[behaviour(ignore)] - tx: RequestResponseSender, - #[behaviour(ignore)] - pending_requests: HashMap, - /// Events that need to be yielded to the outside when polling. - #[behaviour(ignore)] - events: VecDeque, - /// Timeout for pending requests - #[behaviour(ignore)] - timeout: Duration, - /// Interval for request timeout check - #[behaviour(ignore)] - timeout_interval: Interval, -} - -impl RequestResponseBehaviour { - pub fn sender(&self) -> RequestResponseSender { self.tx.clone() } - - pub fn send_response(&mut self, ch: ResponseChannel, rs: PeerResponse) -> Result<(), PeerResponse> { - self.inner.send_response(ch, rs) - } - - pub fn send_request( - &mut self, - peer_id: &PeerId, - request: PeerRequest, - response_tx: oneshot::Sender, - ) -> RequestId { - let request_id = self.inner.send_request(peer_id, request); - let pending_request = PendingRequest { - tx: response_tx, - initiated_at: Instant::now(), - }; - assert!(self.pending_requests.insert(request_id, pending_request).is_none()); - request_id - } - - fn poll_event( - &mut self, - cx: &mut Context, - _params: &mut impl PollParameters, - ) -> Poll::ConnectionHandler>> - { - // poll the `rx` - match self.rx.poll_next_unpin(cx) { - // received a request, forward it through the network and put to the `pending_requests` - Poll::Ready(Some((peer_id, request, response_tx))) => { - let _request_id = self.send_request(&peer_id, request, response_tx); - }, - // the channel was closed - Poll::Ready(None) => panic!("request-response channel has been closed"), - Poll::Pending => (), - } - - if let Some(event) = self.events.pop_front() { - // forward a pending event to the top - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); - } - - while let Poll::Ready(Some(())) = self.timeout_interval.poll_next_unpin(cx) { - let now = Instant::now(); - let timeout = self.timeout; - self.pending_requests.retain(|request_id, pending_request| { - let retain = now.duration_since(pending_request.initiated_at) < timeout; - if !retain { - warn!("Request {} timed out", request_id); - } - retain - }); - } - - Poll::Pending - } - - fn process_request( - &mut self, - peer_id: PeerId, - request: PeerRequest, - response_channel: ResponseChannel, - ) { - self.events.push_back(RequestResponseBehaviourEvent::InboundRequest { - peer_id, - request, - response_channel, - }) - } - - fn process_response(&mut self, request_id: RequestId, response: PeerResponse) { - match self.pending_requests.remove(&request_id) { - Some(pending) => { - if let Err(e) = pending.tx.send(response) { - error!("{:?}. Request {:?} is not processed", e, request_id); - } - }, - _ => error!("Received unknown request {:?}", request_id), - } - } -} - -impl NetworkBehaviourEventProcess> for RequestResponseBehaviour { - fn inject_event(&mut self, event: RequestResponseEvent) { - let (peer_id, message) = match event { - RequestResponseEvent::Message { peer, message } => (peer, message), - RequestResponseEvent::InboundFailure { error, .. } => { - error!("Error on receive a request: {:?}", error); - return; - }, - RequestResponseEvent::OutboundFailure { - peer, - request_id, - error, - } => { - error!("Error on send request {:?} to peer {:?}: {:?}", request_id, peer, error); - let err_response = PeerResponse::Err { - err: format!("{:?}", error), - }; - self.process_response(request_id, err_response); - return; - }, - RequestResponseEvent::ResponseSent { .. } => return, - }; - - match message { - RequestResponseMessage::Request { request, channel, .. } => { - debug!("Received a request from {:?} peer", peer_id); - self.process_request(peer_id, request, channel) - }, - RequestResponseMessage::Response { request_id, response } => { - debug!( - "Received a response to the {:?} request from peer {:?}", - request_id, peer_id - ); - self.process_response(request_id, response) - }, - } - } -} - -#[derive(Clone)] -pub struct Codec { - phantom: std::marker::PhantomData<(Proto, Req, Res)>, -} - -impl Default for Codec { - fn default() -> Self { - Codec { - phantom: Default::default(), - } - } -} - -#[derive(Debug, Clone)] -pub enum Protocol { - Version1, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct PeerRequest { - pub req: Vec, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum PeerResponse { - Ok { res: Vec }, - None, - Err { err: String }, -} - -macro_rules! try_io { - ($e: expr) => { - match $e { - Ok(ok) => ok, - Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)), - } - }; -} - -impl ProtocolName for Protocol { - fn protocol_name(&self) -> &[u8] { - match self { - Protocol::Version1 => b"/request-response/1", - } - } -} - -#[async_trait] -impl< - Proto: Clone + ProtocolName + Send + Sync, - Req: DeserializeOwned + Serialize + Send + Sync, - Res: DeserializeOwned + Serialize + Send + Sync, - > RequestResponseCodec for Codec -{ - type Protocol = Proto; - type Request = Req; - type Response = Res; - - async fn read_request(&mut self, _protocol: &Self::Protocol, io: &mut T) -> io::Result - where - T: AsyncRead + Unpin + Send, - { - read_to_end(io).await - } - - async fn read_response(&mut self, _protocol: &Self::Protocol, io: &mut T) -> io::Result - where - T: AsyncRead + Unpin + Send, - { - read_to_end(io).await - } - - async fn write_request(&mut self, _protocol: &Self::Protocol, io: &mut T, req: Self::Request) -> io::Result<()> - where - T: AsyncWrite + Unpin + Send, - { - write_all(io, &req).await - } - - async fn write_response(&mut self, _protocol: &Self::Protocol, io: &mut T, res: Self::Response) -> io::Result<()> - where - T: AsyncWrite + Unpin + Send, - { - write_all(io, &res).await - } -} - -async fn read_to_end(io: &mut T) -> io::Result -where - T: AsyncRead + Unpin + Send, - M: DeserializeOwned, -{ - match read_length_prefixed(io, MAX_BUFFER_SIZE).await { - Ok(data) => Ok(try_io!(decode_message(&data))), - Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)), - } -} - -async fn write_all(io: &mut T, msg: &M) -> io::Result<()> -where - T: AsyncWrite + Unpin + Send, - M: Serialize, -{ - let data = try_io!(encode_message(msg)); - if data.len() > MAX_BUFFER_SIZE { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Try to send data size over maximum", - )); - } - write_length_prefixed(io, data).await -} diff --git a/mm2src/mm2_libp2p/src/runtime.rs b/mm2src/mm2_libp2p/src/runtime.rs deleted file mode 100644 index 016cf2b455..0000000000 --- a/mm2src/mm2_libp2p/src/runtime.rs +++ /dev/null @@ -1,33 +0,0 @@ -use common::executor::{BoxFutureSpawner, SpawnFuture}; -use futures::Future; -use std::pin::Pin; -use std::sync::Arc; - -#[derive(Clone)] -pub struct SwarmRuntime { - inner: Arc, -} - -impl SwarmRuntime { - pub fn new(spawner: S) -> SwarmRuntime - where - S: BoxFutureSpawner + Send + Sync + 'static, - { - SwarmRuntime { - inner: Arc::new(spawner), - } - } -} - -impl SpawnFuture for SwarmRuntime { - fn spawn(&self, f: F) - where - F: Future + Send + 'static, - { - self.inner.spawn_boxed(Box::new(Box::pin(f))) - } -} - -impl libp2p::core::Executor for SwarmRuntime { - fn exec(&self, future: Pin + Send>>) { self.inner.spawn_boxed(Box::new(future)) } -} diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index d7a1e33bd9..13d53b8eea 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -9,6 +9,7 @@ version = "0.1.0" edition = "2018" [lib] +path = "src/mm2.rs" doctest = false [features] @@ -17,12 +18,12 @@ native = [] # Deprecated track-ctx-pointer = ["common/track-ctx-pointer"] zhtlc-native-tests = ["coins/zhtlc-native-tests"] run-docker-tests = ["coins/run-docker-tests"] -# TODO -enable-solana = [] default = [] trezor-udp = ["crypto/trezor-udp"] # use for tests to connect to trezor emulator over udp run-device-tests = [] -enable-sia = [] +enable-sia = ["coins/enable-sia", "coins_activation/enable-sia"] +sepolia-maker-swap-v2-tests = [] +sepolia-taker-swap-v2-tests = [] [dependencies] async-std = { version = "1.5", features = ["unstable"] } @@ -31,6 +32,7 @@ bitcrypto = { path = "../mm2_bitcoin/crypto" } blake2 = "0.10.6" bytes = "0.4" chain = { path = "../mm2_bitcoin/chain" } +chrono = "0.4" cfg-if = "1.0" coins = { path = "../coins" } coins_activation = { path = "../coins_activation" } @@ -63,22 +65,21 @@ mm2_err_handle = { path = "../mm2_err_handle" } mm2_event_stream = { path = "../mm2_event_stream" } mm2_gui_storage = { path = "../mm2_gui_storage" } mm2_io = { path = "../mm2_io" } -mm2-libp2p = { path = "../mm2_p2p", package = "mm2_p2p" } +mm2_libp2p = { path = "../mm2_p2p", package = "mm2_p2p", features = ["application"] } mm2_metrics = { path = "../mm2_metrics" } -mm2_net = { path = "../mm2_net", features = ["event-stream", "p2p"] } +mm2_net = { path = "../mm2_net" } mm2_number = { path = "../mm2_number" } mm2_rpc = { path = "../mm2_rpc", features = ["rpc_facilities"]} mm2_state_machine = { path = "../mm2_state_machine" } +trading_api = { path = "../trading_api" } num-traits = "0.2" parity-util-mem = "0.11" parking_lot = { version = "0.12.0", features = ["nightly"] } primitives = { path = "../mm2_bitcoin/primitives" } -prost = "0.11" +primitive-types = "0.11.1" +prost = "0.12" rand = { version = "0.7", features = ["std", "small_rng"] } rand6 = { version = "0.6", package = "rand" } -# TODO: Reduce the size of regex by disabling the features we don't use. -# cf. https://github.com/rust-lang/regex/issues/583 -regex = "1" rmp-serde = "0.14.3" rpc = { path = "../mm2_bitcoin/rpc" } rpc_task = { path = "../rpc_task" } @@ -115,7 +116,7 @@ hyper = { version = "0.14.26", features = ["client", "http2", "server", "tcp"] } rcgen = "0.10" rustls = { version = "0.21", default-features = false } rustls-pemfile = "1.0.2" -tokio = { version = "1.20", features = ["io-util", "rt-multi-thread", "net"] } +tokio = { version = "1.20", features = ["io-util", "rt-multi-thread", "net", "signal"] } [target.'cfg(windows)'.dependencies] winapi = "0.3" @@ -124,16 +125,19 @@ winapi = "0.3" coins = { path = "../coins", features = ["for-tests"] } coins_activation = { path = "../coins_activation", features = ["for-tests"] } mm2_test_helpers = { path = "../mm2_test_helpers" } +trading_api = { path = "../trading_api", features = ["mocktopus"] } mocktopus = "0.8.0" testcontainers = "0.15.0" -web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.20.0", default-features = false, features = ["http"] } +web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.20.0", default-features = false, features = ["http-rustls-tls"] } ethabi = { version = "17.0.0" } rlp = { version = "0.5" } ethcore-transaction = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git", rev = "mm2-v2.1.1" } rustc-hex = "2" +sia-rust = { git = "https://github.com/KomodoPlatform/sia-rust", rev = "9f188b80b3213bcb604e7619275251ce08fae808" } +url = { version = "2.2.2", features = ["serde"] } [build-dependencies] chrono = "0.4" gstuff = { version = "0.7", features = ["nightly"] } -prost-build = { version = "0.11", default-features = false } +prost-build = { version = "0.12", default-features = false } regex = "1" diff --git a/mm2src/mm2_main/src/database.rs b/mm2src/mm2_main/src/database.rs index 1017f1fd6b..8f278d3adc 100644 --- a/mm2src/mm2_main/src/database.rs +++ b/mm2src/mm2_main/src/database.rs @@ -1,10 +1,9 @@ /// The module responsible to work with SQLite database /// -#[path = "database/my_orders.rs"] pub mod my_orders; -#[path = "database/my_swaps.rs"] pub mod my_swaps; -#[path = "database/stats_nodes.rs"] pub mod stats_nodes; -#[path = "database/stats_swaps.rs"] pub mod stats_swaps; +pub mod my_swaps; +pub mod stats_nodes; +pub mod stats_swaps; use crate::CREATE_MY_SWAPS_TABLE; use common::log::{debug, error, info}; diff --git a/mm2src/mm2_main/src/database/my_orders.rs b/mm2src/mm2_main/src/database/my_orders.rs index 898d1f1620..7e38c4bfa9 100644 --- a/mm2src/mm2_main/src/database/my_orders.rs +++ b/mm2src/mm2_main/src/database/my_orders.rs @@ -1,6 +1,6 @@ #![allow(deprecated)] // TODO: remove this once rusqlite is >= 0.29 -use crate::mm2::lp_ordermatch::{FilteringOrder, MakerOrder, MyOrdersFilter, RecentOrdersSelectResult, TakerOrder}; +use crate::lp_ordermatch::{FilteringOrder, MakerOrder, MyOrdersFilter, RecentOrdersSelectResult, TakerOrder}; /// This module contains code to work with my_orders table in MM2 SQLite DB use common::log::debug; use common::{now_ms, PagingOptions}; diff --git a/mm2src/mm2_main/src/database/my_swaps.rs b/mm2src/mm2_main/src/database/my_swaps.rs index 55b08f3957..2fe1a85890 100644 --- a/mm2src/mm2_main/src/database/my_swaps.rs +++ b/mm2src/mm2_main/src/database/my_swaps.rs @@ -1,7 +1,7 @@ #![allow(deprecated)] // TODO: remove this once rusqlite is >= 0.29 /// This module contains code to work with my_swaps table in MM2 SQLite DB -use crate::mm2::lp_swap::{MyRecentSwapsUuids, MySwapsFilter, SavedSwap, SavedSwapIo}; +use crate::lp_swap::{MyRecentSwapsUuids, MySwapsFilter, SavedSwap, SavedSwapIo}; use common::log::debug; use common::PagingOptions; use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Result as SqlResult, ToSql}; diff --git a/mm2src/mm2_main/src/database/stats_nodes.rs b/mm2src/mm2_main/src/database/stats_nodes.rs index 7a6330c24a..458f4159bc 100644 --- a/mm2src/mm2_main/src/database/stats_nodes.rs +++ b/mm2src/mm2_main/src/database/stats_nodes.rs @@ -1,5 +1,5 @@ /// This module contains code to work with nodes table for stats collection in MM2 SQLite DB -use crate::mm2::lp_stats::{NodeInfo, NodeVersionStat}; +use crate::lp_stats::{NodeInfo, NodeVersionStat}; use common::log::debug; use db_common::sqlite::rusqlite::{params_from_iter, Error as SqlError, Result as SqlResult}; use mm2_core::mm_ctx::MmArc; @@ -59,13 +59,10 @@ pub fn select_peers_addresses(ctx: &MmArc) -> SqlResult, S } pub fn select_peers_names(ctx: &MmArc) -> SqlResult, SqlError> { - let conn = ctx.sqlite_connection(); - let mut stmt = conn.prepare(SELECT_PEERS_NAMES)?; - let peers_names = stmt + ctx.sqlite_connection() + .prepare(SELECT_PEERS_NAMES)? .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))? - .collect::>>(); - - peers_names + .collect::>>() } pub fn insert_node_version_stat(ctx: &MmArc, node_version_stat: NodeVersionStat) -> SqlResult<()> { diff --git a/mm2src/mm2_main/src/database/stats_swaps.rs b/mm2src/mm2_main/src/database/stats_swaps.rs index b1127b2d36..3322245f01 100644 --- a/mm2src/mm2_main/src/database/stats_swaps.rs +++ b/mm2src/mm2_main/src/database/stats_swaps.rs @@ -1,6 +1,6 @@ #![allow(deprecated)] // TODO: remove this once rusqlite is >= 0.29 -use crate::mm2::lp_swap::{MakerSavedSwap, SavedSwap, SavedSwapIo, TakerSavedSwap}; +use crate::lp_swap::{MakerSavedSwap, SavedSwap, SavedSwapIo, TakerSavedSwap}; use common::log::{debug, error}; use db_common::{owned_named_params, sqlite::{rusqlite::{params_from_iter, Connection, OptionalExtension}, diff --git a/mm2src/mm2_main/src/ext_api.rs b/mm2src/mm2_main/src/ext_api.rs new file mode 100644 index 0000000000..f1b92c145f --- /dev/null +++ b/mm2src/mm2_main/src/ext_api.rs @@ -0,0 +1,3 @@ +//! RPCs for integration with external third party trading APIs. + +pub mod one_inch; diff --git a/mm2src/mm2_main/src/for_tests/iris_nimda_rick_taker_swap.json b/mm2src/mm2_main/src/for_tests/iris_nimda_rick_taker_swap.json index b040229f93..93cab41558 100644 --- a/mm2src/mm2_main/src/for_tests/iris_nimda_rick_taker_swap.json +++ b/mm2src/mm2_main/src/for_tests/iris_nimda_rick_taker_swap.json @@ -145,12 +145,14 @@ "Started", "Negotiated", "TakerFeeSent", + "TakerPaymentInstructionsReceived", "MakerPaymentReceived", "MakerPaymentWaitConfirmStarted", "MakerPaymentValidatedAndConfirmed", "TakerPaymentSent", "TakerPaymentSpent", "MakerPaymentSpent", + "MakerPaymentSpendConfirmed", "Finished" ], "error_events": [ @@ -164,8 +166,12 @@ "TakerPaymentDataSendFailed", "TakerPaymentWaitForSpendFailed", "MakerPaymentSpendFailed", + "MakerPaymentSpendConfirmFailed", "TakerPaymentWaitRefundStarted", + "TakerPaymentRefundStarted", "TakerPaymentRefunded", - "TakerPaymentRefundFailed" + "TakerPaymentRefundedByWatcher", + "TakerPaymentRefundFailed", + "TakerPaymentRefundFinished" ] } \ No newline at end of file diff --git a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_taker_saved.json b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_taker_saved.json index 07ebe0d6b9..73261f641e 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_taker_saved.json +++ b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_taker_saved.json @@ -49,6 +49,6 @@ "taker_coin":"MORTY", "gui":"atomicDEX 0.5.1 iOS", "mm_version":"1b065636a", - "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"], + "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"], "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"] } \ No newline at end of file diff --git a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_taker_saved.json b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_taker_saved.json index 8fe8622f74..a3158581c6 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_taker_saved.json +++ b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_taker_saved.json @@ -61,6 +61,6 @@ "taker_coin":"MORTY", "gui":"atomicDEX 0.5.1 iOS", "mm_version":"1b065636a", - "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"], + "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"], "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"] } \ No newline at end of file diff --git a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json index cfb9ab66f8..1c1cc39acf 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json +++ b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json @@ -57,6 +57,6 @@ "taker_coin":"MORTY", "gui":null, "mm_version":"", - "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"], - "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed","TakerPaymentRefundFinished"] + "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"], + "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed","TakerPaymentRefundFinished"] } \ No newline at end of file diff --git a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json index cb3146b2a3..e743669805 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json +++ b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json @@ -53,6 +53,6 @@ "taker_coin":"MORTY", "gui":null, "mm_version":"", - "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"], - "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed","TakerPaymentRefundFinished"] + "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"], + "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed","TakerPaymentRefundFinished"] } \ No newline at end of file diff --git a/mm2src/mm2_main/src/lib.rs b/mm2src/mm2_main/src/lib.rs deleted file mode 100644 index 23c7b65312..0000000000 --- a/mm2src/mm2_main/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![feature(hash_raw_entry)] -// `mockable` implementation uses these -#![allow( - clippy::forget_ref, - clippy::forget_copy, - clippy::swap_ptr_to_ref, - clippy::forget_non_drop, - clippy::let_unit_value -)] - -#[macro_use] extern crate common; -#[macro_use] extern crate gstuff; -#[macro_use] extern crate serde_json; -#[macro_use] extern crate serde_derive; -#[macro_use] extern crate ser_error_derive; -#[cfg(test)] extern crate mm2_test_helpers; - -pub mod mm2; - -#[cfg(all(target_arch = "wasm32", test))] mod wasm_tests; diff --git a/mm2src/mm2_main/src/lp_dispatcher.rs b/mm2src/mm2_main/src/lp_dispatcher.rs index 74f7a9a18d..8ea662873e 100644 --- a/mm2src/mm2_main/src/lp_dispatcher.rs +++ b/mm2src/mm2_main/src/lp_dispatcher.rs @@ -1,5 +1,5 @@ -use crate::mm2::lp_ordermatch::TradingBotEvent; -use crate::mm2::lp_swap::MakerSwapStatusChanged; +use crate::lp_ordermatch::TradingBotEvent; +use crate::lp_swap::MakerSwapStatusChanged; use async_std::sync::RwLock; use mm2_core::{event_dispatcher::{Dispatcher, EventUniqueId}, mm_ctx::{from_ctx, MmArc}}; diff --git a/mm2src/mm2_main/src/lp_healthcheck.rs b/mm2src/mm2_main/src/lp_healthcheck.rs new file mode 100644 index 0000000000..20a6004c95 --- /dev/null +++ b/mm2src/mm2_main/src/lp_healthcheck.rs @@ -0,0 +1,446 @@ +use async_std::prelude::FutureExt; +use chrono::Utc; +use common::executor::SpawnFuture; +use common::expirable_map::ExpirableEntry; +use common::{log, HttpStatusCode, StatusCode}; +use derive_more::Display; +use futures::channel::oneshot::{self, Receiver, Sender}; +use instant::{Duration, Instant}; +use lazy_static::lazy_static; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::MmError; +use mm2_err_handle::prelude::*; +use mm2_libp2p::p2p_ctx::P2PContext; +use mm2_libp2p::{decode_message, encode_message, pub_sub_topic, Libp2pPublic, PeerAddress, TopicPrefix}; +use ser_error_derive::SerializeErrorType; +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; +use std::sync::Mutex; + +use crate::lp_network::{broadcast_p2p_msg, P2PRequestError, P2PRequestResult}; + +pub(crate) const PEER_HEALTHCHECK_PREFIX: TopicPrefix = "hcheck"; + +const fn healthcheck_message_exp_secs() -> u64 { + #[cfg(test)] + return 3; + + #[cfg(not(test))] + 10 +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(any(test, target_arch = "wasm32"), derive(PartialEq))] +pub(crate) struct HealthcheckMessage { + #[serde(deserialize_with = "deserialize_bytes")] + signature: Vec, + data: HealthcheckData, +} + +#[derive(Debug, Display)] +enum SignValidationError { + #[display( + fmt = "Healthcheck message is expired. Current time in UTC: {now_secs}, healthcheck `expires_at` in UTC: {expires_at_secs}" + )] + Expired { now_secs: u64, expires_at_secs: u64 }, + #[display( + fmt = "Healthcheck message have too high expiration time. Max allowed expiration seconds: {max_allowed_expiration_secs}, received message expiration seconds: {remaining_expiration_secs}" + )] + LifetimeOverflow { + max_allowed_expiration_secs: u64, + remaining_expiration_secs: u64, + }, + #[display(fmt = "Public key is not valid.")] + InvalidPublicKey, + #[display(fmt = "Signature integrity doesn't match with the public key.")] + FakeSignature, + #[display(fmt = "Process failed unexpectedly due to this reason: {reason}")] + Internal { reason: String }, +} + +impl HealthcheckMessage { + pub(crate) fn generate_message(ctx: &MmArc, is_a_reply: bool) -> Result { + let p2p_ctx = P2PContext::fetch_from_mm_arc(ctx); + let keypair = p2p_ctx.keypair(); + let sender_public_key = keypair.public().encode_protobuf(); + + let data = HealthcheckData { + sender_public_key, + expires_at_secs: u64::try_from(Utc::now().timestamp()).map_err(|e| e.to_string())? + + healthcheck_message_exp_secs(), + is_a_reply, + }; + + let signature = try_s!(keypair.sign(&try_s!(data.encode()))); + + Ok(Self { signature, data }) + } + + fn generate_or_use_cached_message(ctx: &MmArc) -> Result { + const MIN_DURATION_FOR_REUSABLE_MSG: Duration = Duration::from_secs(5); + + lazy_static! { + static ref RECENTLY_GENERATED_MESSAGE: Mutex> = + Mutex::new(ExpirableEntry::new( + // Using dummy values in order to initialize `HealthcheckMessage` context. + HealthcheckMessage { + signature: vec![], + data: HealthcheckData { + sender_public_key: vec![], + expires_at_secs: 0, + is_a_reply: false, + }, + }, + Duration::from_secs(0) + )); + } + + // If recently generated message has longer life than `MIN_DURATION_FOR_REUSABLE_MSG`, we can reuse it to + // reduce the message generation overhead under high pressure. + let mut mutexed_msg = RECENTLY_GENERATED_MESSAGE.lock().unwrap(); + + if mutexed_msg.has_longer_life_than(MIN_DURATION_FOR_REUSABLE_MSG) { + Ok(mutexed_msg.get_element().clone()) + } else { + let new_msg = HealthcheckMessage::generate_message(ctx, true)?; + + mutexed_msg.update_value(new_msg.clone()); + mutexed_msg.update_expiration(Instant::now() + Duration::from_secs(healthcheck_message_exp_secs())); + + Ok(new_msg) + } + } + + fn is_received_message_valid(&self) -> Result { + let now_secs = u64::try_from(Utc::now().timestamp()) + .map_err(|e| SignValidationError::Internal { reason: e.to_string() })?; + + let remaining_expiration_secs = self.data.expires_at_secs.saturating_sub(now_secs); + + if remaining_expiration_secs == 0 { + return Err(SignValidationError::Expired { + now_secs, + expires_at_secs: self.data.expires_at_secs, + }); + } else if remaining_expiration_secs > healthcheck_message_exp_secs() { + return Err(SignValidationError::LifetimeOverflow { + max_allowed_expiration_secs: healthcheck_message_exp_secs(), + remaining_expiration_secs, + }); + } + + let Ok(public_key) = Libp2pPublic::try_decode_protobuf(&self.data.sender_public_key) else { + log::debug!("Couldn't decode public key from the healthcheck message."); + + return Err(SignValidationError::InvalidPublicKey); + }; + + let encoded_message = self + .data + .encode() + .map_err(|e| SignValidationError::Internal { reason: e.to_string() })?; + + if public_key.verify(&encoded_message, &self.signature) { + Ok(public_key.to_peer_id().into()) + } else { + Err(SignValidationError::FakeSignature) + } + } + + #[inline] + pub(crate) fn encode(&self) -> Result, rmp_serde::encode::Error> { encode_message(self) } + + #[inline] + pub(crate) fn decode(bytes: &[u8]) -> Result { decode_message(bytes) } + + #[inline] + pub(crate) fn should_reply(&self) -> bool { !self.data.is_a_reply } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(any(test, target_arch = "wasm32"), derive(PartialEq))] +struct HealthcheckData { + #[serde(deserialize_with = "deserialize_bytes")] + sender_public_key: Vec, + expires_at_secs: u64, + is_a_reply: bool, +} + +impl HealthcheckData { + #[inline] + fn encode(&self) -> Result, rmp_serde::encode::Error> { encode_message(self) } +} + +#[inline] +pub fn peer_healthcheck_topic(peer_address: &PeerAddress) -> String { + pub_sub_topic(PEER_HEALTHCHECK_PREFIX, &peer_address.to_string()) +} + +#[derive(Deserialize)] +pub struct RequestPayload { + peer_address: PeerAddress, +} + +fn deserialize_bytes<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + struct ByteVisitor; + + impl<'de> serde::de::Visitor<'de> for ByteVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a non-empty byte array up to 512 bytes") + } + + fn visit_seq(self, mut seq: A) -> Result, A::Error> + where + A: serde::de::SeqAccess<'de>, + { + let mut buffer = vec![]; + while let Some(byte) = seq.next_element()? { + if buffer.len() >= 512 { + return Err(serde::de::Error::invalid_length( + buffer.len(), + &"longest possible length allowed for this field is 512 bytes (with RSA algorithm).", + )); + } + + buffer.push(byte); + } + + if buffer.is_empty() { + return Err(serde::de::Error::custom("Can't be empty.")); + } + + Ok(buffer) + } + } + + deserializer.deserialize_seq(ByteVisitor) +} + +#[derive(Debug, Display, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum HealthcheckRpcError { + MessageGenerationFailed { reason: String }, + MessageEncodingFailed { reason: String }, + Internal { reason: String }, +} + +impl HttpStatusCode for HealthcheckRpcError { + fn status_code(&self) -> common::StatusCode { + match self { + HealthcheckRpcError::MessageGenerationFailed { .. } + | HealthcheckRpcError::Internal { .. } + | HealthcheckRpcError::MessageEncodingFailed { .. } => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +pub async fn peer_connection_healthcheck_rpc( + ctx: MmArc, + req: RequestPayload, +) -> Result> { + // When things go awry, we want records to clear themselves to keep the memory clean of unused data. + // This is unrelated to the timeout logic. + let address_record_exp = Duration::from_secs(healthcheck_message_exp_secs()); + + let target_peer_address = req.peer_address; + + let p2p_ctx = P2PContext::fetch_from_mm_arc(&ctx); + if target_peer_address == p2p_ctx.peer_id().into() { + // That's us, so return true. + return Ok(true); + } + + let message = HealthcheckMessage::generate_message(&ctx, false) + .map_err(|reason| HealthcheckRpcError::MessageGenerationFailed { reason })?; + + let encoded_message = message + .encode() + .map_err(|e| HealthcheckRpcError::MessageEncodingFailed { reason: e.to_string() })?; + + let (tx, rx): (Sender<()>, Receiver<()>) = oneshot::channel(); + + { + let mut book = ctx.healthcheck_response_handler.lock().await; + book.insert(target_peer_address.into(), tx, address_record_exp); + } + + broadcast_p2p_msg( + &ctx, + peer_healthcheck_topic(&target_peer_address), + encoded_message, + None, + ); + + let timeout_duration = Duration::from_secs(healthcheck_message_exp_secs()); + Ok(rx.timeout(timeout_duration).await == Ok(Ok(()))) +} + +pub(crate) async fn process_p2p_healthcheck_message( + ctx: &MmArc, + message: mm2_libp2p::GossipsubMessage, +) -> P2PRequestResult<()> { + macro_rules! try_or_return { + ($exp:expr, $msg: expr) => { + match $exp { + Ok(t) => t, + Err(e) => { + log::error!("{}, error: {e:?}", $msg); + return; + }, + } + }; + } + + let data = HealthcheckMessage::decode(&message.data) + .map_to_mm(|e| P2PRequestError::DecodeError(format!("Couldn't decode healthcheck message: {}", e)))?; + let sender_peer = data.is_received_message_valid().map_to_mm(|e| { + P2PRequestError::ValidationFailed(format!("Received an invalid healthcheck message. Error: {}", e)) + })?; + + let ctx = ctx.clone(); + + // Pass the remaining work to another thread to free up this one as soon as possible, + // so KDF can handle a high amount of healthcheck messages more efficiently. + ctx.spawner().spawn(async move { + if data.should_reply() { + // Reply the message so they know we are healthy. + + let msg = try_or_return!( + HealthcheckMessage::generate_or_use_cached_message(&ctx), + "Couldn't generate the healthcheck message, this is very unusual!" + ); + + let encoded_msg = try_or_return!( + msg.encode(), + "Couldn't encode healthcheck message, this is very unusual!" + ); + + let topic = peer_healthcheck_topic(&sender_peer); + broadcast_p2p_msg(&ctx, topic, encoded_msg, None); + } else { + // The requested peer is healthy; signal the response channel. + let mut response_handler = ctx.healthcheck_response_handler.lock().await; + if let Some(tx) = response_handler.remove(&sender_peer.into()) { + if tx.send(()).is_err() { + log::error!("Result channel isn't present for peer '{sender_peer}'."); + }; + } else { + log::info!("Peer '{sender_peer}' isn't recorded in the healthcheck response handler."); + }; + } + }); + + Ok(()) +} + +#[cfg(any(test, target_arch = "wasm32"))] +mod tests { + use std::mem::discriminant; + use std::str::FromStr; + + use super::*; + use common::cross_test; + use crypto::CryptoCtx; + use mm2_libp2p::behaviours::atomicdex::generate_ed25519_keypair; + use mm2_test_helpers::for_tests::mm_ctx_with_iguana; + + common::cfg_wasm32! { + use wasm_bindgen_test::*; + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + } + + fn ctx() -> MmArc { + let ctx = mm_ctx_with_iguana(Some("dummy-value")); + let p2p_key = { + let crypto_ctx = CryptoCtx::from_ctx(&ctx).unwrap(); + let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); + key.take() + }; + + let (cmd_tx, _) = futures::channel::mpsc::channel(0); + + let p2p_context = P2PContext::new(cmd_tx, generate_ed25519_keypair(p2p_key)); + p2p_context.store_to_mm_arc(&ctx); + + ctx + } + + cross_test!(test_peer_address, { + #[derive(Deserialize, Serialize)] + struct PeerAddressTest { + peer_address: PeerAddress, + } + + let address_str = "12D3KooWEtuv7kmgGCC7oAQ31hB7AR5KkhT3eEWB2bP2roo3M7rY"; + let json_content = format!("{{\"peer_address\": \"{address_str}\"}}"); + let address_struct: PeerAddressTest = serde_json::from_str(&json_content).unwrap(); + + let actual_peer_id = mm2_libp2p::PeerId::from_str(address_str).unwrap(); + let deserialized_peer_id: mm2_libp2p::PeerId = address_struct.peer_address.into(); + + assert_eq!(deserialized_peer_id, actual_peer_id); + }); + + cross_test!(test_valid_message, { + let ctx = ctx(); + let message = HealthcheckMessage::generate_message(&ctx, false).unwrap(); + message.is_received_message_valid().unwrap(); + }); + + cross_test!(test_corrupted_messages, { + let ctx = ctx(); + + let mut message = HealthcheckMessage::generate_message(&ctx, false).unwrap(); + message.data.expires_at_secs += healthcheck_message_exp_secs() * 3; + assert_eq!( + discriminant(&message.is_received_message_valid().err().unwrap()), + discriminant(&SignValidationError::LifetimeOverflow { + max_allowed_expiration_secs: 0, + remaining_expiration_secs: 0 + }) + ); + + let mut message = HealthcheckMessage::generate_message(&ctx, false).unwrap(); + message.data.is_a_reply = !message.data.is_a_reply; + assert_eq!( + discriminant(&message.is_received_message_valid().err().unwrap()), + discriminant(&SignValidationError::FakeSignature) + ); + + let mut message = HealthcheckMessage::generate_message(&ctx, false).unwrap(); + message.data.sender_public_key.push(0); + assert_eq!( + discriminant(&message.is_received_message_valid().err().unwrap()), + discriminant(&SignValidationError::InvalidPublicKey) + ); + }); + + cross_test!(test_expired_message, { + let ctx = ctx(); + let message = HealthcheckMessage::generate_message(&ctx, false).unwrap(); + common::executor::Timer::sleep(3.).await; + assert_eq!( + discriminant(&message.is_received_message_valid().err().unwrap()), + discriminant(&SignValidationError::Expired { + now_secs: 0, + expires_at_secs: 0 + }) + ); + }); + + cross_test!(test_encode_decode, { + let ctx = ctx(); + let original = HealthcheckMessage::generate_message(&ctx, false).unwrap(); + + let encoded = original.encode().unwrap(); + assert!(!encoded.is_empty()); + + let decoded = HealthcheckMessage::decode(&encoded).unwrap(); + assert_eq!(original, decoded); + }); +} diff --git a/mm2src/mm2_main/src/lp_init/init_context.rs b/mm2src/mm2_main/src/lp_init/init_context.rs index e1c045c098..8b03751b69 100644 --- a/mm2src/mm2_main/src/lp_init/init_context.rs +++ b/mm2src/mm2_main/src/lp_init/init_context.rs @@ -1,6 +1,6 @@ -use crate::mm2::lp_native_dex::init_hw::InitHwTaskManagerShared; +use crate::lp_native_dex::init_hw::InitHwTaskManagerShared; #[cfg(target_arch = "wasm32")] -use crate::mm2::lp_native_dex::init_metamask::InitMetamaskManagerShared; +use crate::lp_native_dex::init_metamask::InitMetamaskManagerShared; use mm2_core::mm_ctx::{from_ctx, MmArc}; use rpc_task::RpcTaskManager; use std::sync::Arc; diff --git a/mm2src/mm2_main/src/lp_init/init_hw.rs b/mm2src/mm2_main/src/lp_init/init_hw.rs index 18e97bc096..b9d0c67664 100644 --- a/mm2src/mm2_main/src/lp_init/init_hw.rs +++ b/mm2src/mm2_main/src/lp_init/init_hw.rs @@ -1,4 +1,4 @@ -use crate::mm2::lp_native_dex::init_context::MmInitContext; +use crate::lp_native_dex::init_context::MmInitContext; use async_trait::async_trait; use common::{HttpStatusCode, SuccessResponse}; use crypto::hw_rpc_task::{HwConnectStatuses, HwRpcTaskAwaitingStatus, HwRpcTaskUserAction, HwRpcTaskUserActionRequest, diff --git a/mm2src/mm2_main/src/lp_init/init_metamask.rs b/mm2src/mm2_main/src/lp_init/init_metamask.rs index f80afe5878..630f6a1796 100644 --- a/mm2src/mm2_main/src/lp_init/init_metamask.rs +++ b/mm2src/mm2_main/src/lp_init/init_metamask.rs @@ -1,4 +1,4 @@ -use crate::mm2::lp_native_dex::init_context::MmInitContext; +use crate::lp_native_dex::init_context::MmInitContext; use async_trait::async_trait; use common::{HttpStatusCode, SerdeInfallible, SuccessResponse}; use crypto::metamask::{from_metamask_error, MetamaskError, MetamaskRpcError, WithMetamaskRpcError}; diff --git a/mm2src/mm2_main/src/lp_message_service.rs b/mm2src/mm2_main/src/lp_message_service.rs index 018d6bf971..0804bfbfa8 100644 --- a/mm2src/mm2_main/src/lp_message_service.rs +++ b/mm2src/mm2_main/src/lp_message_service.rs @@ -1,6 +1,6 @@ #[path = "notification/telegram/telegram.rs"] pub mod telegram; -use crate::mm2::lp_message_service::telegram::{ChatIdRegistry, TelegramError, TgClient}; +use crate::lp_message_service::telegram::{ChatIdRegistry, TelegramError, TgClient}; use async_trait::async_trait; use derive_more::Display; use futures::lock::Mutex as AsyncMutex; @@ -121,8 +121,8 @@ pub async fn init_message_service(ctx: &MmArc) -> Result<(), MmError Vec { #[cfg(not(target_arch = "wasm32"))] fn default_seednodes(netid: u16) -> Vec { - use crate::mm2::lp_network::addr_to_ipv4_string; + use crate::lp_network::addr_to_ipv4_string; if netid == 8762 { DEFAULT_NETID_SEEDNODES .iter() @@ -476,7 +476,9 @@ pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { let balance_update_ordermatch_handler = BalanceUpdateOrdermatchHandler::new(ctx.clone()); register_balance_update_handler(ctx.clone(), Box::new(balance_update_ordermatch_handler)).await; - ctx.initialized.pin(true).map_to_mm(MmInitError::Internal)?; + ctx.initialized + .set(true) + .map_to_mm(|_| MmInitError::Internal("Already Initialized".to_string()))?; // launch kickstart threads before RPC is available, this will prevent the API user to place // an order and start new swap that might get started 2 times because of kick-start @@ -499,9 +501,11 @@ pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { pub async fn lp_init(ctx: MmArc, version: String, datetime: String) -> MmInitResult<()> { info!("Version: {} DT {}", version, datetime); + // Ensure the database root directory exists before initializing the wallet passphrase. + // This is necessary to store the encrypted wallet passphrase if needed. #[cfg(not(target_arch = "wasm32"))] { - let dbdir = ctx.dbdir(); + let dbdir = ctx.db_root(); fs::create_dir_all(&dbdir).map_to_mm(|e| MmInitError::ErrorCreatingDbDir { path: dbdir.clone(), error: e.to_string(), @@ -559,6 +563,20 @@ async fn kick_start(ctx: MmArc) -> MmInitResult<()> { Ok(()) } +fn get_p2p_key(ctx: &MmArc, i_am_seed: bool) -> P2PResult<[u8; 32]> { + // TODO: Use persistent peer ID regardless the node type. + if i_am_seed { + if let Ok(crypto_ctx) = CryptoCtx::from_ctx(ctx) { + let key = sha256(crypto_ctx.mm2_internal_privkey_slice()); + return Ok(key.take()); + } + } + + let mut p2p_key = [0; 32]; + common::os_rng(&mut p2p_key).map_err(|e| P2PInitError::Internal(e.to_string()))?; + Ok(p2p_key) +} + pub async fn init_p2p(ctx: MmArc) -> P2PResult<()> { let i_am_seed = ctx.conf["i_am_seed"].as_bool().unwrap_or(false); let netid = ctx.netid(); @@ -570,13 +588,8 @@ pub async fn init_p2p(ctx: MmArc) -> P2PResult<()> { let seednodes = seednodes(&ctx)?; let ctx_on_poll = ctx.clone(); - let force_p2p_key = if i_am_seed { - let crypto_ctx = CryptoCtx::from_ctx(&ctx).mm_err(|e| P2PInitError::Internal(e.to_string()))?; - let key = sha256(crypto_ctx.mm2_internal_privkey_slice()); - Some(key.take()) - } else { - None - }; + + let p2p_key = get_p2p_key(&ctx, i_am_seed)?; let node_type = if i_am_seed { relay_node_type(&ctx).await? @@ -591,9 +604,8 @@ pub async fn init_p2p(ctx: MmArc) -> P2PResult<()> { .try_into() .unwrap_or(usize::MAX); - let mut gossipsub_config = GossipsubConfig::new(netid, spawner, node_type); + let mut gossipsub_config = GossipsubConfig::new(netid, spawner, node_type, p2p_key); gossipsub_config.to_dial(seednodes); - gossipsub_config.force_key(force_p2p_key); gossipsub_config.max_num_streams(max_num_streams); let spawn_result = spawn_gossipsub(gossipsub_config, move |swarm| { @@ -626,14 +638,18 @@ pub async fn init_p2p(ctx: MmArc) -> P2PResult<()> { ); }) .await; + let (cmd_tx, event_rx, peer_id) = spawn_result?; - ctx.peer_id.pin(peer_id.to_string()).map_to_mm(P2PInitError::Internal)?; - let p2p_context = P2PContext::new(cmd_tx); + + let p2p_context = P2PContext::new(cmd_tx, generate_ed25519_keypair(p2p_key)); p2p_context.store_to_mm_arc(&ctx); let fut = p2p_event_process_loop(ctx.weak(), event_rx, i_am_seed); ctx.spawner().spawn(fut); + // Listen for health check messages. + subscribe_to_topic(&ctx, peer_healthcheck_topic(&peer_id.into())); + Ok(()) } diff --git a/mm2src/mm2_main/src/lp_network.rs b/mm2src/mm2_main/src/lp_network.rs index cd2b32c538..b2ef53f3fb 100644 --- a/mm2src/mm2_main/src/lp_network.rs +++ b/mm2src/mm2_main/src/lp_network.rs @@ -29,17 +29,17 @@ use instant::Instant; use keys::KeyPair; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; +use mm2_libp2p::application::request_response::P2PRequest; +use mm2_libp2p::p2p_ctx::P2PContext; use mm2_libp2p::{decode_message, encode_message, DecodingError, GossipsubEvent, GossipsubMessage, Libp2pPublic, Libp2pSecpPublic, MessageId, NetworkPorts, PeerId, TOPIC_SEPARATOR}; use mm2_libp2p::{AdexBehaviourCmd, AdexBehaviourEvent, AdexEventRx, AdexResponse}; use mm2_libp2p::{PeerAddresses, RequestResponseBehaviourEvent}; use mm2_metrics::{mm_label, mm_timing}; -use mm2_net::p2p::P2PContext; use serde::de; use std::net::ToSocketAddrs; -use crate::mm2::lp_ordermatch; -use crate::mm2::{lp_stats, lp_swap}; +use crate::{lp_healthcheck, lp_ordermatch, lp_stats, lp_swap}; pub type P2PRequestResult = Result>; pub type P2PProcessResult = Result>; @@ -62,6 +62,7 @@ pub enum P2PRequestError { ResponseError(String), #[display(fmt = "Expected 1 response, found {}", _0)] ExpectedSingleResponseError(usize), + ValidationFailed(String), } /// Enum covering error cases that can happen during P2P message processing. @@ -88,12 +89,6 @@ impl From for P2PRequestError { fn from(e: rmp_serde::decode::Error) -> Self { P2PRequestError::DecodeError(e.to_string()) } } -#[derive(Eq, Debug, Deserialize, PartialEq, Serialize)] -pub enum P2PRequest { - Ordermatch(lp_ordermatch::OrdermatchRequest), - NetworkInfo(lp_stats::NetworkInfoRequest), -} - pub async fn p2p_event_process_loop(ctx: MmWeak, mut rx: AdexEventRx, i_am_relay: bool) { loop { let adex_event = rx.next().await; @@ -196,15 +191,16 @@ async fn process_p2p_message( to_propagate = true; }, Some(lp_swap::TX_HELPER_PREFIX) => { - if let Some(pair) = split.next() { - if let Ok(Some(coin)) = lp_coinfind(&ctx, pair).await { + if let Some(ticker) = split.next() { + if let Ok(Some(coin)) = lp_coinfind(&ctx, ticker).await { if let Err(e) = coin.tx_enum_from_bytes(&message.data) { log::error!("Message cannot continue the process due to: {:?}", e); return; }; - let fut = coin.send_raw_tx_bytes(&message.data); - ctx.spawner().spawn(async { + if coin.is_utxo_in_native_mode() { + let fut = coin.send_raw_tx_bytes(&message.data); + ctx.spawner().spawn(async { match fut.compat().await { Ok(id) => log::debug!("Transaction broadcasted successfully: {:?} ", id), // TODO (After https://github.com/KomodoPlatform/atomicDEX-API/pull/1433) @@ -213,8 +209,19 @@ async fn process_p2p_message( Err(e) => log::error!("Broadcast transaction failed (ignore this error if the transaction already sent by another seednode). {}", e), }; }) + } } + + to_propagate = true; + } + }, + Some(lp_healthcheck::PEER_HEALTHCHECK_PREFIX) => { + if let Err(e) = lp_healthcheck::process_p2p_healthcheck_message(&ctx, message).await { + log::error!("{}", e); + return; } + + to_propagate = true; }, None | Some(_) => (), } diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 86e2fb0491..620cb79bfb 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -21,7 +21,6 @@ // use async_trait::async_trait; -use best_orders::BestOrdersAction; use blake2::digest::{Update, VariableOutput}; use blake2::Blake2bVar; use coins::utxo::{compressed_pub_key_from_priv_raw, ChecksumType, UtxoAddressFormat}; @@ -42,6 +41,8 @@ use http::Response; use keys::{AddressFormat, KeyPair}; use mm2_core::mm_ctx::{from_ctx, MmArc, MmWeak}; use mm2_err_handle::prelude::*; +use mm2_libp2p::application::request_response::ordermatch::OrdermatchRequest; +use mm2_libp2p::application::request_response::P2PRequest; use mm2_libp2p::{decode_signed, encode_and_sign, encode_message, pub_sub_topic, PublicKey, TopicHash, TopicPrefix, TOPIC_SEPARATOR}; use mm2_metrics::mm_gauge; @@ -69,20 +70,19 @@ use std::time::Duration; use trie_db::NodeCodec as NodeCodecT; use uuid::Uuid; -use crate::mm2::lp_network::{broadcast_p2p_msg, request_any_relay, request_one_peer, subscribe_to_topic, P2PRequest, - P2PRequestError}; -use crate::mm2::lp_swap::maker_swap_v2::{self, MakerSwapStateMachine, MakerSwapStorage}; -use crate::mm2::lp_swap::taker_swap_v2::{self, TakerSwapStateMachine, TakerSwapStorage}; -use crate::mm2::lp_swap::{calc_max_maker_vol, check_balance_for_maker_swap, check_balance_for_taker_swap, - check_other_coin_balance_for_swap, detect_secret_hash_algo, dex_fee_amount_from_taker_coin, - generate_secret, get_max_maker_vol, insert_new_swap_to_db, is_pubkey_banned, - lp_atomic_locktime, p2p_keypair_and_peer_id_to_broadcast, - p2p_private_and_peer_id_to_broadcast, run_maker_swap, run_taker_swap, swap_v2_topic, - AtomicLocktimeVersion, CheckBalanceError, CheckBalanceResult, CoinVolumeInfo, MakerSwap, - RunMakerSwapInput, RunTakerSwapInput, SwapConfirmationsSettings, TakerSwap, LEGACY_SWAP_TYPE}; +use crate::lp_network::{broadcast_p2p_msg, request_any_relay, request_one_peer, subscribe_to_topic, P2PRequestError}; +use crate::lp_swap::maker_swap_v2::{self, MakerSwapStateMachine, MakerSwapStorage}; +use crate::lp_swap::taker_swap_v2::{self, TakerSwapStateMachine, TakerSwapStorage}; +use crate::lp_swap::{calc_max_maker_vol, check_balance_for_maker_swap, check_balance_for_taker_swap, + check_other_coin_balance_for_swap, detect_secret_hash_algo, dex_fee_amount_from_taker_coin, + generate_secret, get_max_maker_vol, insert_new_swap_to_db, is_pubkey_banned, lp_atomic_locktime, + p2p_keypair_and_peer_id_to_broadcast, p2p_private_and_peer_id_to_broadcast, run_maker_swap, + run_taker_swap, swap_v2_topic, AtomicLocktimeVersion, CheckBalanceError, CheckBalanceResult, + CoinVolumeInfo, MakerSwap, RunMakerSwapInput, RunTakerSwapInput, SwapConfirmationsSettings, + TakerSwap, LEGACY_SWAP_TYPE}; #[cfg(any(test, feature = "run-docker-tests"))] -use crate::mm2::lp_swap::taker_swap::FailAt; +use crate::lp_swap::taker_swap::FailAt; pub use best_orders::{best_orders_rpc, best_orders_rpc_v2}; pub use orderbook_depth::orderbook_depth_rpc; @@ -95,25 +95,21 @@ cfg_wasm32! { pub type OrdermatchDbLocked<'a> = DbLocked<'a, OrdermatchDb>; } -#[path = "lp_ordermatch/best_orders.rs"] mod best_orders; -#[path = "lp_ordermatch/lp_bot.rs"] mod lp_bot; +mod best_orders; +mod lp_bot; pub use lp_bot::{start_simple_market_maker_bot, stop_simple_market_maker_bot, StartSimpleMakerBotRequest, TradingBotEvent}; -#[path = "lp_ordermatch/my_orders_storage.rs"] mod my_orders_storage; -#[path = "lp_ordermatch/new_protocol.rs"] mod new_protocol; -#[path = "lp_ordermatch/order_requests_tracker.rs"] +mod new_protocol; mod order_requests_tracker; -#[path = "lp_ordermatch/orderbook_depth.rs"] mod orderbook_depth; -#[path = "lp_ordermatch/orderbook_rpc.rs"] mod orderbook_rpc; +mod orderbook_depth; +mod orderbook_rpc; #[cfg(all(test, not(target_arch = "wasm32")))] #[path = "ordermatch_tests.rs"] pub mod ordermatch_tests; -#[cfg(target_arch = "wasm32")] -#[path = "lp_ordermatch/ordermatch_wasm_db.rs"] -mod ordermatch_wasm_db; +#[cfg(target_arch = "wasm32")] mod ordermatch_wasm_db; pub const ORDERBOOK_PREFIX: TopicPrefix = "orbk"; #[cfg(not(test))] @@ -126,6 +122,7 @@ const TAKER_ORDER_TIMEOUT: u64 = 30; const ORDER_MATCH_TIMEOUT: u64 = 30; const ORDERBOOK_REQUESTING_TIMEOUT: u64 = MIN_ORDER_KEEP_ALIVE_INTERVAL * 2; const MAX_ORDERS_NUMBER_IN_ORDERBOOK_RESPONSE: usize = 1000; +const RECENTLY_CANCELLED_TIMEOUT: Duration = Duration::from_secs(120); #[cfg(not(test))] const TRIE_STATE_HISTORY_TIMEOUT: u64 = 14400; #[cfg(test)] @@ -335,6 +332,12 @@ async fn process_orders_keep_alive( Ok(()) } +#[inline] +fn process_maker_order_created(ctx: &MmArc, from_pubkey: String, created_msg: new_protocol::MakerOrderCreated) { + let order: OrderbookItem = (created_msg, from_pubkey).into(); + insert_or_update_order(ctx, order); +} + fn process_maker_order_updated( ctx: MmArc, from_pubkey: String, @@ -354,6 +357,22 @@ fn process_maker_order_updated( Ok(()) } +fn process_maker_order_cancelled(ctx: &MmArc, from_pubkey: String, cancelled_msg: new_protocol::MakerOrderCancelled) { + let uuid = Uuid::from(cancelled_msg.uuid); + let ordermatch_ctx = OrdermatchContext::from_ctx(ctx).expect("from_ctx failed"); + let mut orderbook = ordermatch_ctx.orderbook.lock(); + // Add the order to the recently cancelled list to ignore it if a new order with the same uuid + // is received within the `RECENTLY_CANCELLED_TIMEOUT` timeframe. + // We do this even if the order is in the order_set, because it could have been added through + // means other than the order creation message. + orderbook.recently_cancelled.insert(uuid, from_pubkey.clone()); + if let Some(order) = orderbook.order_set.get(&uuid) { + if order.pubkey == from_pubkey { + orderbook.remove_order_trie_update(uuid); + } + } +} + // fn verify_pubkey_orderbook(orderbook: &GetOrderbookPubkeyItem) -> Result<(), String> { // let keys: Vec<(_, _)> = orderbook // .orders @@ -460,16 +479,6 @@ fn insert_or_update_my_order(ctx: &MmArc, item: OrderbookItem, my_order: &MakerO } } -fn delete_order(ctx: &MmArc, pubkey: &str, uuid: Uuid) { - let ordermatch_ctx = OrdermatchContext::from_ctx(ctx).expect("from_ctx failed"); - let mut orderbook = ordermatch_ctx.orderbook.lock(); - if let Some(order) = orderbook.order_set.get(&uuid) { - if order.pubkey == pubkey { - orderbook.remove_order_trie_update(uuid); - } - } -} - fn delete_my_order(ctx: &MmArc, uuid: Uuid, p2p_privkey: Option) { let ordermatch_ctx: Arc = OrdermatchContext::from_ctx(ctx).expect("from_ctx failed"); let mut orderbook = ordermatch_ctx.orderbook.lock(); @@ -552,8 +561,7 @@ pub async fn process_msg(ctx: MmArc, from_peer: String, msg: &[u8], i_am_relay: log::debug!("received ordermatch message {:?}", message); match message { new_protocol::OrdermatchMessage::MakerOrderCreated(created_msg) => { - let order: OrderbookItem = (created_msg, hex::encode(pubkey.to_bytes().as_slice())).into(); - insert_or_update_order(&ctx, order); + process_maker_order_created(&ctx, pubkey.to_hex(), created_msg); Ok(()) }, new_protocol::OrdermatchMessage::PubkeyKeepAlive(keep_alive) => { @@ -580,7 +588,7 @@ pub async fn process_msg(ctx: MmArc, from_peer: String, msg: &[u8], i_am_relay: Ok(()) }, new_protocol::OrdermatchMessage::MakerOrderCancelled(cancelled_msg) => { - delete_order(&ctx, &pubkey.to_hex(), cancelled_msg.uuid.into()); + process_maker_order_cancelled(&ctx, pubkey.to_hex(), cancelled_msg); Ok(()) }, new_protocol::OrdermatchMessage::MakerOrderUpdated(updated_msg) => { @@ -592,34 +600,6 @@ pub async fn process_msg(ctx: MmArc, from_peer: String, msg: &[u8], i_am_relay: } } -#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] -pub enum OrdermatchRequest { - /// Get an orderbook for the given pair. - GetOrderbook { - base: String, - rel: String, - }, - /// Sync specific pubkey orderbook state if our known Patricia trie state doesn't match the latest keep alive message - SyncPubkeyOrderbookState { - pubkey: String, - /// Request using this condition - trie_roots: HashMap, - }, - BestOrders { - coin: String, - action: BestOrdersAction, - volume: BigRational, - }, - OrderbookDepth { - pairs: Vec<(String, String)>, - }, - BestOrdersByNumber { - coin: String, - action: BestOrdersAction, - number: usize, - }, -} - #[derive(Debug)] struct TryFromBytesError(String); @@ -1087,15 +1067,15 @@ fn maker_order_updated_p2p_notify( broadcast_p2p_msg(&ctx, topic, encoded_msg, peer_id); } -fn maker_order_cancelled_p2p_notify(ctx: MmArc, order: &MakerOrder) { +fn maker_order_cancelled_p2p_notify(ctx: &MmArc, order: &MakerOrder) { let message = new_protocol::OrdermatchMessage::MakerOrderCancelled(new_protocol::MakerOrderCancelled { uuid: order.uuid.into(), timestamp: now_sec(), pair_trie_root: H64::default(), }); - delete_my_order(&ctx, order.uuid, order.p2p_privkey); + delete_my_order(ctx, order.uuid, order.p2p_privkey); log::debug!("maker_order_cancelled_p2p_notify called, message {:?}", message); - broadcast_ordermatch_message(&ctx, order.orderbook_topic(), message, order.p2p_keypair()); + broadcast_ordermatch_message(ctx, order.orderbook_topic(), message, order.p2p_keypair()); } pub struct BalanceUpdateOrdermatchHandler { @@ -1145,7 +1125,7 @@ impl BalanceTradeFeeUpdatedHandler for BalanceUpdateOrdermatchHandler { // This checks that the order hasn't been removed by another process if removed_order_mutex.is_some() { // cancel the order - maker_order_cancelled_p2p_notify(ctx.clone(), &order); + maker_order_cancelled_p2p_notify(&ctx, &order); delete_my_maker_order( ctx.clone(), order.clone(), @@ -2481,7 +2461,6 @@ fn collect_orderbook_metrics(ctx: &MmArc, orderbook: &Orderbook) { mm_gauge!(ctx.metrics, "orderbook.memory_db", memory_db_size as f64); } -#[derive(Default)] struct Orderbook { /// A map from (base, rel). ordered: HashMap<(String, String), BTreeSet>, @@ -2494,12 +2473,34 @@ struct Orderbook { order_set: HashMap, /// a map of orderbook states of known maker pubkeys pubkeys_state: HashMap, + /// The `TimeCache` of recently canceled orders, mapping `Uuid` to the maker pubkey as `String`, + /// used to avoid order recreation in case of out-of-order p2p messages, + /// e.g., when receiving the order cancellation message before the order is created. + /// Entries are kept for `RECENTLY_CANCELLED_TIMEOUT` seconds. + recently_cancelled: TimeCache, topics_subscribed_to: HashMap, /// MemoryDB instance to store Patricia Tries data memory_db: MemoryDB, my_p2p_pubkeys: HashSet, } +impl Default for Orderbook { + fn default() -> Self { + Orderbook { + ordered: HashMap::default(), + pairs_existing_for_base: HashMap::default(), + pairs_existing_for_rel: HashMap::default(), + unordered: HashMap::default(), + order_set: HashMap::default(), + pubkeys_state: HashMap::default(), + recently_cancelled: TimeCache::new(RECENTLY_CANCELLED_TIMEOUT), + topics_subscribed_to: HashMap::default(), + memory_db: MemoryDB::default(), + my_p2p_pubkeys: HashSet::default(), + } + } +} + fn hashed_null_node() -> TrieHash { ::hashed_null_node() } impl Orderbook { @@ -2516,6 +2517,12 @@ impl Orderbook { fn find_order_by_uuid(&self, uuid: &Uuid) -> Option { self.order_set.get(uuid).cloned() } fn insert_or_update_order_update_trie(&mut self, order: OrderbookItem) { + // Ignore the order if it was recently cancelled + if self.recently_cancelled.get(&order.uuid) == Some(&order.pubkey) { + warn!("Maker order {} was recently cancelled, ignoring", order.uuid); + return; + } + let zero = BigRational::from_integer(0.into()); if order.max_volume <= zero || order.price <= zero || order.min_volume < zero { self.remove_order_trie_update(order.uuid); @@ -3318,6 +3325,7 @@ pub async fn lp_ordermatch_loop(ctx: MmArc) { // This checks that the order hasn't been removed by another process if let Some(order_mutex) = removed_order_mutex { let order = order_mutex.lock().await; + maker_order_cancelled_p2p_notify(&ctx, &order); delete_my_maker_order( ctx.clone(), order.clone(), @@ -3434,7 +3442,7 @@ async fn check_balance_for_maker_orders(ctx: MmArc, ordermatch_ctx: &OrdermatchC let removed_order_mutex = ordermatch_ctx.maker_orders_ctx.lock().remove_order(&uuid); // This checks that the order hasn't been removed by another process if removed_order_mutex.is_some() { - maker_order_cancelled_p2p_notify(ctx.clone(), &order); + maker_order_cancelled_p2p_notify(&ctx, &order); delete_my_maker_order(ctx.clone(), order.clone(), reason) .compat() .await @@ -4743,7 +4751,7 @@ async fn cancel_previous_maker_orders( let removed_order_mutex = ordermatch_ctx.maker_orders_ctx.lock().remove_order(&uuid); // This checks that the uuid, &order.base hasn't been removed by another process if removed_order_mutex.is_some() { - maker_order_cancelled_p2p_notify(ctx.clone(), &order); + maker_order_cancelled_p2p_notify(ctx, &order); delete_my_maker_order(ctx.clone(), order.clone(), MakerOrderCancellationReason::Cancelled) .compat() .await @@ -5131,6 +5139,7 @@ pub struct CancelOrderResponse { result: String, } +// TODO: This is a near copy of the function below, `cancel_order_rpc`. pub async fn cancel_order(ctx: MmArc, req: CancelOrderReq) -> Result> { let ordermatch_ctx = match OrdermatchContext::from_ctx(&ctx) { Ok(x) => x, @@ -5145,7 +5154,7 @@ pub async fn cancel_order(ctx: MmArc, req: CancelOrderReq) -> Result Result> let removed_order_mutex = ordermatch_ctx.maker_orders_ctx.lock().remove_order(&order.uuid); // This checks that the order hasn't been removed by another process if removed_order_mutex.is_some() { - maker_order_cancelled_p2p_notify(ctx.clone(), &order); + maker_order_cancelled_p2p_notify(&ctx, &order); delete_my_maker_order(ctx, order.clone(), MakerOrderCancellationReason::Cancelled) .compat() .await @@ -5543,7 +5552,7 @@ pub async fn cancel_orders_by(ctx: &MmArc, cancel_by: CancelBy) -> Result<(Vec { - MmError::err(OrderbookAddrErr::CoinIsNotSupported(coin.to_owned())) - }, CoinProtocol::ZHTLC { .. } => Ok(OrderbookAddress::Shielded), #[cfg(not(target_arch = "wasm32"))] // Todo: Shielded address is used for lightning for now, the lightning node public key can be used for the orderbook entry pubkey diff --git a/mm2src/mm2_main/src/lp_ordermatch/best_orders.rs b/mm2src/mm2_main/src/lp_ordermatch/best_orders.rs index 7e20ed46bc..3bf684b66c 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/best_orders.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/best_orders.rs @@ -4,6 +4,8 @@ use derive_more::Display; use http::{Response, StatusCode}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use mm2_libp2p::application::request_response::{ordermatch::{BestOrdersAction, OrdermatchRequest}, + P2PRequest}; use mm2_number::{BigRational, MmNumber}; use mm2_rpc::data::legacy::OrderConfirmationsSettings; use num_traits::Zero; @@ -12,15 +14,8 @@ use std::collections::{HashMap, HashSet}; use uuid::Uuid; use super::{addr_format_from_protocol_info, is_my_order, mm2_internal_pubkey_hex, orderbook_address, - BaseRelProtocolInfo, OrderbookP2PItemWithProof, OrdermatchContext, OrdermatchRequest, RpcOrderbookEntryV2}; -use crate::mm2::lp_network::{request_any_relay, P2PRequest}; - -#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum BestOrdersAction { - Buy, - Sell, -} + BaseRelProtocolInfo, OrderbookP2PItemWithProof, OrdermatchContext, RpcOrderbookEntryV2}; +use crate::lp_network::request_any_relay; #[derive(Debug, Deserialize)] pub struct BestOrdersRequest { @@ -404,8 +399,8 @@ pub async fn best_orders_rpc_v2( #[cfg(all(test, not(target_arch = "wasm32")))] mod best_orders_test { use super::*; - use crate::mm2::lp_ordermatch::ordermatch_tests::make_random_orders; - use crate::mm2::lp_ordermatch::{OrderbookItem, TrieProof}; + use crate::lp_ordermatch::ordermatch_tests::make_random_orders; + use crate::lp_ordermatch::{OrderbookItem, TrieProof}; use common::new_uuid; use std::iter::FromIterator; diff --git a/mm2src/mm2_main/src/lp_ordermatch/lp_bot.rs b/mm2src/mm2_main/src/lp_ordermatch/lp_bot.rs index acdb6bd217..9c8095253b 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/lp_bot.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/lp_bot.rs @@ -16,11 +16,11 @@ use std::ops::Deref; use std::{collections::HashMap, sync::Arc}; #[path = "simple_market_maker.rs"] mod simple_market_maker_bot; -use crate::mm2::lp_dispatcher::{LpEvents, StopCtxEvent}; -use crate::mm2::lp_message_service::{MessageServiceContext, MAKER_BOT_ROOM_ID}; -use crate::mm2::lp_ordermatch::lp_bot::simple_market_maker_bot::{tear_down_bot, BOT_DEFAULT_REFRESH_RATE, - PRECISION_FOR_NOTIFICATION}; -use crate::mm2::lp_swap::MakerSwapStatusChanged; +use crate::lp_dispatcher::{LpEvents, StopCtxEvent}; +use crate::lp_message_service::{MessageServiceContext, MAKER_BOT_ROOM_ID}; +use crate::lp_ordermatch::lp_bot::simple_market_maker_bot::{tear_down_bot, BOT_DEFAULT_REFRESH_RATE, + PRECISION_FOR_NOTIFICATION}; +use crate::lp_swap::MakerSwapStatusChanged; pub use simple_market_maker_bot::{start_simple_market_maker_bot, stop_simple_market_maker_bot, StartSimpleMakerBotRequest}; diff --git a/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs b/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs index 677af4be71..05322325a5 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs @@ -205,11 +205,10 @@ pub trait MyOrdersFilteringHistory { #[cfg(not(target_arch = "wasm32"))] mod native_impl { use super::*; - use crate::mm2::database::my_orders::{insert_maker_order, insert_taker_order, select_orders_by_filter, - select_status_by_uuid, update_maker_order, update_order_status, - update_was_taker}; - use crate::mm2::lp_ordermatch::{my_maker_order_file_path, my_maker_orders_dir, my_order_history_file_path, - my_taker_order_file_path, my_taker_orders_dir}; + use crate::database::my_orders::{insert_maker_order, insert_taker_order, select_orders_by_filter, + select_status_by_uuid, update_maker_order, update_order_status, update_was_taker}; + use crate::lp_ordermatch::{my_maker_order_file_path, my_maker_orders_dir, my_order_history_file_path, + my_taker_order_file_path, my_taker_orders_dir}; use mm2_io::fs::{read_dir_json, read_json, remove_file_async, write_json, FsJsonError}; const USE_TMP_FILE: bool = true; @@ -350,10 +349,10 @@ mod native_impl { #[cfg(target_arch = "wasm32")] mod wasm_impl { use super::*; - use crate::mm2::lp_ordermatch::ordermatch_wasm_db::{DbTransactionError, InitDbError, MyActiveMakerOrdersTable, - MyActiveTakerOrdersTable, MyFilteringHistoryOrdersTable, - MyHistoryOrdersTable}; - use crate::mm2::lp_ordermatch::OrdermatchContext; + use crate::lp_ordermatch::ordermatch_wasm_db::{DbTransactionError, InitDbError, MyActiveMakerOrdersTable, + MyActiveTakerOrdersTable, MyFilteringHistoryOrdersTable, + MyHistoryOrdersTable}; + use crate::lp_ordermatch::OrdermatchContext; use common::log::warn; use mm2_rpc::data::legacy::TakerAction; use num_traits::ToPrimitive; @@ -694,8 +693,8 @@ mod wasm_impl { mod tests { use super::wasm_impl::{maker_order_to_filtering_history_item, taker_order_to_filtering_history_item}; use super::*; - use crate::mm2::lp_ordermatch::ordermatch_wasm_db::{ItemId, MyFilteringHistoryOrdersTable}; - use crate::mm2::lp_ordermatch::{OrdermatchContext, TakerRequest}; + use crate::lp_ordermatch::ordermatch_wasm_db::{ItemId, MyFilteringHistoryOrdersTable}; + use crate::lp_ordermatch::{OrdermatchContext, TakerRequest}; use common::{new_uuid, now_ms}; use futures::compat::Future01CompatExt; use itertools::Itertools; diff --git a/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs b/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs index b15f5e6d38..72aa53597d 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs @@ -5,7 +5,7 @@ use mm2_rpc::data::legacy::{MatchBy as SuperMatchBy, OrderConfirmationsSettings, use std::collections::{HashMap, HashSet}; use uuid::Uuid; -use crate::mm2::lp_ordermatch::{AlbOrderedOrderbookPair, H64}; +use crate::lp_ordermatch::{AlbOrderedOrderbookPair, H64}; #[derive(Debug, Deserialize, Serialize)] #[allow(clippy::large_enum_variant)] diff --git a/mm2src/mm2_main/src/lp_ordermatch/orderbook_depth.rs b/mm2src/mm2_main/src/lp_ordermatch/orderbook_depth.rs index dcc991a361..1772acbe61 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/orderbook_depth.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/orderbook_depth.rs @@ -1,9 +1,10 @@ -use super::{orderbook_topic_from_base_rel, OrdermatchContext, OrdermatchRequest}; -use crate::mm2::lp_network::{request_any_relay, P2PRequest}; +use super::{orderbook_topic_from_base_rel, OrdermatchContext}; +use crate::lp_network::request_any_relay; use coins::is_wallet_only_ticker; use common::log; use http::Response; use mm2_core::mm_ctx::MmArc; +use mm2_libp2p::application::request_response::{ordermatch::OrdermatchRequest, P2PRequest}; use serde_json::{self as json, Value as Json}; use std::collections::HashMap; diff --git a/mm2src/mm2_main/src/lp_ordermatch/ordermatch_wasm_db.rs b/mm2src/mm2_main/src/lp_ordermatch/ordermatch_wasm_db.rs index 3e6c1665c0..d64e0b40df 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/ordermatch_wasm_db.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/ordermatch_wasm_db.rs @@ -40,7 +40,7 @@ impl Deref for OrdermatchDb { pub mod tables { use super::*; - use crate::mm2::lp_ordermatch::{MakerOrder, Order, TakerOrder}; + use crate::lp_ordermatch::{MakerOrder, Order, TakerOrder}; use serde_json::Value as Json; #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] diff --git a/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs b/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs index 728f177176..14551167d6 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs @@ -1,14 +1,14 @@ -use crate::mm2::lp_dispatcher::{dispatch_lp_event, DispatcherContext}; -use crate::mm2::lp_ordermatch::lp_bot::{RunningState, StoppedState, StoppingState, TradingBotStarted, - TradingBotStopped, TradingBotStopping, VolumeSettings}; -use crate::mm2::lp_ordermatch::{cancel_all_orders, CancelBy, TradingBotEvent}; -use crate::mm2::lp_swap::SavedSwap; -use crate::mm2::{lp_ordermatch::{cancel_order, create_maker_order, - lp_bot::{SimpleCoinMarketMakerCfg, SimpleMakerBotRegistry, TradingBotContext, - TradingBotState}, - update_maker_order, CancelOrderReq, MakerOrder, MakerOrderUpdateReq, - OrdermatchContext, SetPriceReq}, - lp_swap::{latest_swaps_for_pair, LatestSwapsErr}}; +use crate::lp_dispatcher::{dispatch_lp_event, DispatcherContext}; +use crate::lp_ordermatch::lp_bot::{RunningState, StoppedState, StoppingState, TradingBotStarted, TradingBotStopped, + TradingBotStopping, VolumeSettings}; +use crate::lp_ordermatch::{cancel_all_orders, CancelBy, TradingBotEvent}; +use crate::lp_swap::SavedSwap; +use crate::{lp_ordermatch::{cancel_order, create_maker_order, + lp_bot::{SimpleCoinMarketMakerCfg, SimpleMakerBotRegistry, TradingBotContext, + TradingBotState}, + update_maker_order, CancelOrderReq, MakerOrder, MakerOrderUpdateReq, OrdermatchContext, + SetPriceReq}, + lp_swap::{latest_swaps_for_pair, LatestSwapsErr}}; use coins::lp_price::{fetch_price_tickers, Provider, RateInfos, PRICE_ENDPOINTS}; use coins::{lp_coinfind, GetNonZeroBalance}; use common::{executor::{SpawnFuture, Timer}, diff --git a/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker_tests.rs b/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker_tests.rs index 8928656415..7e12bcf5f3 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker_tests.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker_tests.rs @@ -1,6 +1,6 @@ -use crate::mm2::{lp_ordermatch::lp_bot::simple_market_maker_bot::vwap, - lp_ordermatch::lp_bot::SimpleCoinMarketMakerCfg, - lp_swap::{MakerSavedSwap, SavedSwap}}; +use crate::{lp_ordermatch::lp_bot::simple_market_maker_bot::vwap, + lp_ordermatch::lp_bot::SimpleCoinMarketMakerCfg, + lp_swap::{MakerSavedSwap, SavedSwap}}; use common::{block_on, log::UnifiedLoggerBuilder}; use mm2_number::MmNumber; diff --git a/mm2src/mm2_main/src/lp_stats.rs b/mm2src/mm2_main/src/lp_stats.rs index cf87f68c3d..185996ecd1 100644 --- a/mm2src/mm2_main/src/lp_stats.rs +++ b/mm2src/mm2_main/src/lp_stats.rs @@ -7,13 +7,14 @@ use futures::lock::Mutex as AsyncMutex; use http::StatusCode; use mm2_core::mm_ctx::{from_ctx, MmArc}; use mm2_err_handle::prelude::*; +use mm2_libp2p::application::request_response::network_info::NetworkInfoRequest; use mm2_libp2p::{encode_message, NetworkInfo, PeerId, RelayAddress, RelayAddressError}; use serde_json::{self as json, Value as Json}; use std::collections::{HashMap, HashSet}; use std::sync::Arc; -use crate::mm2::lp_network::{add_reserved_peer_addresses, lp_network_ports, request_peers, NetIdError, P2PRequest, - ParseAddressError, PeerDecodedResponse}; +use crate::lp_network::{add_reserved_peer_addresses, lp_network_ports, request_peers, NetIdError, ParseAddressError, + PeerDecodedResponse}; use std::str::FromStr; pub type NodeVersionResult = Result>; @@ -90,7 +91,7 @@ fn insert_node_info_to_db(_ctx: &MmArc, _node_info: &NodeInfo) -> Result<(), Str #[cfg(not(target_arch = "wasm32"))] fn insert_node_info_to_db(ctx: &MmArc, node_info: &NodeInfo) -> Result<(), String> { - crate::mm2::database::stats_nodes::insert_node_info(ctx, node_info).map_err(|e| e.to_string()) + crate::database::stats_nodes::insert_node_info(ctx, node_info).map_err(|e| e.to_string()) } #[cfg(target_arch = "wasm32")] @@ -98,7 +99,7 @@ fn insert_node_version_stat_to_db(_ctx: &MmArc, _node_version_stat: NodeVersionS #[cfg(not(target_arch = "wasm32"))] fn insert_node_version_stat_to_db(ctx: &MmArc, node_version_stat: NodeVersionStat) -> Result<(), String> { - crate::mm2::database::stats_nodes::insert_node_version_stat(ctx, node_version_stat).map_err(|e| e.to_string()) + crate::database::stats_nodes::insert_node_version_stat(ctx, node_version_stat).map_err(|e| e.to_string()) } #[cfg(target_arch = "wasm32")] @@ -106,7 +107,7 @@ fn delete_node_info_from_db(_ctx: &MmArc, _name: String) -> Result<(), String> { #[cfg(not(target_arch = "wasm32"))] fn delete_node_info_from_db(ctx: &MmArc, name: String) -> Result<(), String> { - crate::mm2::database::stats_nodes::delete_node_info(ctx, name).map_err(|e| e.to_string()) + crate::database::stats_nodes::delete_node_info(ctx, name).map_err(|e| e.to_string()) } #[cfg(target_arch = "wasm32")] @@ -114,7 +115,7 @@ fn select_peers_addresses_from_db(_ctx: &MmArc) -> Result, #[cfg(not(target_arch = "wasm32"))] fn select_peers_addresses_from_db(ctx: &MmArc) -> Result, String> { - crate::mm2::database::stats_nodes::select_peers_addresses(ctx).map_err(|e| e.to_string()) + crate::database::stats_nodes::select_peers_addresses(ctx).map_err(|e| e.to_string()) } #[cfg(target_arch = "wasm32")] @@ -125,7 +126,7 @@ pub async fn add_node_to_version_stat(_ctx: MmArc, _req: Json) -> NodeVersionRes /// Adds node info. to db to be used later for stats collection #[cfg(not(target_arch = "wasm32"))] pub async fn add_node_to_version_stat(ctx: MmArc, req: Json) -> NodeVersionResult { - use crate::mm2::lp_network::addr_to_ipv4_string; + use crate::lp_network::addr_to_ipv4_string; let node_info: NodeInfo = json::from_value(req)?; @@ -169,12 +170,6 @@ struct Mm2VersionRes { nodes: HashMap, } -#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] -pub enum NetworkInfoRequest { - /// Get MM2 version of nodes added to stats collection - GetMm2Version, -} - fn process_get_version_request(ctx: MmArc) -> Result>, String> { let response = ctx.mm_version().to_string(); let encoded = try_s!(encode_message(&response)); @@ -188,18 +183,15 @@ pub fn process_info_request(ctx: MmArc, request: NetworkInfoRequest) -> Result Self { StatsCollectionStatus::Stopped } -} - #[cfg_attr(not(target_arch = "wasm32"), derive(Default))] struct StatsContext { pub status: AsyncMutex, @@ -267,8 +259,9 @@ pub async fn start_version_stat_collection(ctx: MmArc, req: Json) -> NodeVersion #[cfg(not(target_arch = "wasm32"))] async fn stat_collection_loop(ctx: MmArc, interval: f64) { use common::now_sec; + use mm2_libp2p::application::request_response::P2PRequest; - use crate::mm2::database::stats_nodes::select_peers_names; + use crate::database::stats_nodes::select_peers_names; let mut interval = interval; loop { diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 184f8e3c05..0acb7fc443 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -58,9 +58,9 @@ // use super::lp_network::P2PRequestResult; -use crate::mm2::lp_network::{broadcast_p2p_msg, Libp2pPeerId, P2PProcessError, P2PProcessResult, P2PRequestError}; -use crate::mm2::lp_swap::maker_swap_v2::{MakerSwapStateMachine, MakerSwapStorage}; -use crate::mm2::lp_swap::taker_swap_v2::{TakerSwapStateMachine, TakerSwapStorage}; +use crate::lp_network::{broadcast_p2p_msg, Libp2pPeerId, P2PProcessError, P2PProcessResult, P2PRequestError}; +use crate::lp_swap::maker_swap_v2::{MakerSwapStateMachine, MakerSwapStorage}; +use crate::lp_swap::taker_swap_v2::{TakerSwapStateMachine, TakerSwapStorage}; use bitcrypto::{dhash160, sha256}; use coins::{lp_coinfind, lp_coinfind_or_err, CoinFindError, DexFee, MmCoin, MmCoinEnum, TradeFee, TransactionEnum}; use common::log::{debug, warn}; @@ -94,30 +94,27 @@ use uuid::Uuid; #[cfg(feature = "custom-swap-locktime")] use std::sync::atomic::{AtomicU64, Ordering}; -#[path = "lp_swap/check_balance.rs"] mod check_balance; -#[path = "lp_swap/maker_swap.rs"] mod maker_swap; -#[path = "lp_swap/maker_swap_v2.rs"] pub mod maker_swap_v2; -#[path = "lp_swap/max_maker_vol_rpc.rs"] mod max_maker_vol_rpc; -#[path = "lp_swap/my_swaps_storage.rs"] mod my_swaps_storage; -#[path = "lp_swap/pubkey_banning.rs"] mod pubkey_banning; -#[path = "lp_swap/recreate_swap_data.rs"] mod recreate_swap_data; -#[path = "lp_swap/saved_swap.rs"] mod saved_swap; -#[path = "lp_swap/swap_lock.rs"] mod swap_lock; +mod check_balance; +mod maker_swap; +pub mod maker_swap_v2; +mod max_maker_vol_rpc; +mod my_swaps_storage; +mod pubkey_banning; +mod recreate_swap_data; +mod saved_swap; +mod swap_lock; #[path = "lp_swap/komodefi.swap_v2.pb.rs"] #[rustfmt::skip] mod swap_v2_pb; -#[path = "lp_swap/swap_v2_common.rs"] mod swap_v2_common; -#[path = "lp_swap/swap_v2_rpcs.rs"] pub(crate) mod swap_v2_rpcs; -#[path = "lp_swap/swap_watcher.rs"] pub(crate) mod swap_watcher; -#[path = "lp_swap/taker_restart.rs"] +mod swap_v2_common; +pub(crate) mod swap_v2_rpcs; +pub(crate) mod swap_watcher; pub(crate) mod taker_restart; -#[path = "lp_swap/taker_swap.rs"] pub(crate) mod taker_swap; -#[path = "lp_swap/taker_swap_v2.rs"] pub mod taker_swap_v2; -#[path = "lp_swap/trade_preimage.rs"] mod trade_preimage; +pub(crate) mod taker_swap; +pub mod taker_swap_v2; +mod trade_preimage; -#[cfg(target_arch = "wasm32")] -#[path = "lp_swap/swap_wasm_db.rs"] -mod swap_wasm_db; +#[cfg(target_arch = "wasm32")] mod swap_wasm_db; pub use check_balance::{check_other_coin_balance_for_swap, CheckBalanceError, CheckBalanceResult}; use coins::utxo::utxo_standard::UtxoStandardCoin; @@ -154,8 +151,11 @@ pub const TX_HELPER_PREFIX: TopicPrefix = "txhlp"; pub(crate) const LEGACY_SWAP_TYPE: u8 = 0; pub(crate) const MAKER_SWAP_V2_TYPE: u8 = 1; pub(crate) const TAKER_SWAP_V2_TYPE: u8 = 2; -const MAX_STARTED_AT_DIFF: u64 = 60; +pub(crate) const TAKER_FEE_VALIDATION_ATTEMPTS: usize = 6; +pub(crate) const TAKER_FEE_VALIDATION_RETRY_DELAY_SECS: f64 = 10.; + +const MAX_STARTED_AT_DIFF: u64 = 60; const NEGOTIATE_SEND_INTERVAL: f64 = 30.; /// If a certain P2P message is not received, swap will be aborted after this time expires. @@ -1019,7 +1019,7 @@ pub async fn insert_new_swap_to_db( #[cfg(not(target_arch = "wasm32"))] fn add_swap_to_db_index(ctx: &MmArc, swap: &SavedSwap) { if let Some(conn) = ctx.sqlite_conn_opt() { - crate::mm2::database::stats_swaps::add_swap_to_index(&conn, swap) + crate::database::stats_swaps::add_swap_to_index(&conn, swap) } } @@ -1625,18 +1625,15 @@ pub async fn active_swaps_rpc(ctx: MmArc, req: Json) -> Result> } /// Algorithm used to hash swap secret. -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize, Default)] pub enum SecretHashAlgo { /// ripemd160(sha256(secret)) + #[default] DHASH160 = 1, /// sha256(secret) SHA256 = 2, } -impl Default for SecretHashAlgo { - fn default() -> Self { SecretHashAlgo::DHASH160 } -} - #[derive(Debug, Display)] pub struct UnsupportedSecretHashAlgo(u8); @@ -1839,9 +1836,9 @@ pub(crate) const NO_REFUND_FEE: bool = false; #[cfg(all(test, not(target_arch = "wasm32")))] mod lp_swap_tests { use super::*; - use crate::mm2::lp_native_dex::{fix_directories, init_p2p}; + use crate::lp_native_dex::{fix_directories, init_p2p}; use coins::hd_wallet::HDPathAccountToAddressId; - use coins::utxo::rpc_clients::ElectrumRpcRequest; + use coins::utxo::rpc_clients::ElectrumConnectionSettings; use coins::utxo::utxo_standard::utxo_standard_coin_with_priv_key; use coins::utxo::{UtxoActivationParams, UtxoRpcMode}; use coins::MarketCoinOps; @@ -2223,12 +2220,15 @@ mod lp_swap_tests { mode: UtxoRpcMode::Electrum { servers: electrums .iter() - .map(|url| ElectrumRpcRequest { + .map(|url| ElectrumConnectionSettings { url: url.to_string(), protocol: Default::default(), disable_cert_verification: false, + timeout_sec: None, }) .collect(), + min_connected: None, + max_connected: None, }, utxo_merge_params: None, tx_history: false, diff --git a/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs b/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs index 130fb2e3e1..38c6e07106 100644 --- a/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs +++ b/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs @@ -1,3 +1,4 @@ +// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SignedMessage { diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index b1117e707a..0eb72b8a71 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -9,12 +9,13 @@ use super::{broadcast_my_swap_status, broadcast_p2p_tx_msg, broadcast_swap_msg_e wait_for_maker_payment_conf_until, AtomicSwap, LockedAmount, MySwapInfo, NegotiationDataMsg, NegotiationDataV2, NegotiationDataV3, RecoveredSwap, RecoveredSwapAction, SavedSwap, SavedSwapIo, SavedTradeFee, SecretHashAlgo, SwapConfirmationsSettings, SwapError, SwapMsg, SwapPubkeys, SwapTxDataMsg, - SwapsContext, TransactionIdentifier, INCLUDE_REFUND_FEE, NO_REFUND_FEE, WAIT_CONFIRM_INTERVAL_SEC}; -use crate::mm2::lp_dispatcher::{DispatcherContext, LpEvents}; -use crate::mm2::lp_network::subscribe_to_topic; -use crate::mm2::lp_ordermatch::MakerOrderBuilder; -use crate::mm2::lp_swap::swap_v2_common::mark_swap_as_finished; -use crate::mm2::lp_swap::{broadcast_swap_message, taker_payment_spend_duration, MAX_STARTED_AT_DIFF}; + SwapsContext, TransactionIdentifier, INCLUDE_REFUND_FEE, NO_REFUND_FEE, TAKER_FEE_VALIDATION_ATTEMPTS, + TAKER_FEE_VALIDATION_RETRY_DELAY_SECS, WAIT_CONFIRM_INTERVAL_SEC}; +use crate::lp_dispatcher::{DispatcherContext, LpEvents}; +use crate::lp_network::subscribe_to_topic; +use crate::lp_ordermatch::MakerOrderBuilder; +use crate::lp_swap::swap_v2_common::mark_swap_as_finished; +use crate::lp_swap::{broadcast_swap_message, taker_payment_spend_duration, MAX_STARTED_AT_DIFF}; use coins::lp_price::fetch_swap_coins_price; use coins::{CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MmCoin, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, @@ -767,18 +768,17 @@ impl MakerSwap { min_block_number: taker_coin_start_block, uuid: self.uuid.as_bytes(), }) - .compat() .await { Ok(_) => break, Err(err) => { - if attempts >= 6 { + if attempts >= TAKER_FEE_VALIDATION_ATTEMPTS { return Ok((Some(MakerSwapCommand::Finish), vec![ MakerSwapEvent::TakerFeeValidateFailed(ERRL!("{}", err).into()), ])); } else { attempts += 1; - Timer::sleep(10.).await; + Timer::sleep(TAKER_FEE_VALIDATION_RETRY_DELAY_SECS).await; } }, }; @@ -794,7 +794,8 @@ impl MakerSwap { } async fn maker_payment(&self) -> Result<(Option, Vec), String> { - let timeout = self.r().data.started_at + self.r().data.lock_duration / 3; + let lock_duration = self.r().data.lock_duration; + let timeout = self.r().data.started_at + lock_duration / 3; let now = now_sec(); if now > timeout { return Ok((Some(MakerSwapCommand::Finish), vec![ @@ -802,24 +803,24 @@ impl MakerSwap { ])); } + let maker_payment_lock = self.r().data.maker_payment_lock; + let other_maker_coin_htlc_pub = self.r().other_maker_coin_htlc_pub; let secret_hash = self.secret_hash(); + let maker_coin_swap_contract_address = self.r().data.maker_coin_swap_contract_address.clone(); let unique_data = self.unique_swap_data(); - let transaction_f = self - .maker_coin - .check_if_my_payment_sent(CheckIfMyPaymentSentArgs { - time_lock: self.r().data.maker_payment_lock, - other_pub: &*self.r().other_maker_coin_htlc_pub, - secret_hash: secret_hash.as_slice(), - search_from_block: self.r().data.maker_coin_start_block, - swap_contract_address: &self.r().data.maker_coin_swap_contract_address, - swap_unique_data: &unique_data, - amount: &self.maker_amount, - payment_instructions: &self.r().payment_instructions, - }) - .compat(); + let payment_instructions = self.r().payment_instructions.clone(); + let transaction_f = self.maker_coin.check_if_my_payment_sent(CheckIfMyPaymentSentArgs { + time_lock: maker_payment_lock, + other_pub: &*other_maker_coin_htlc_pub, + secret_hash: secret_hash.as_slice(), + search_from_block: self.r().data.maker_coin_start_block, + swap_contract_address: &maker_coin_swap_contract_address, + swap_unique_data: &unique_data, + amount: &self.maker_amount, + payment_instructions: &payment_instructions, + }); - let wait_maker_payment_until = - wait_for_maker_payment_conf_until(self.r().data.started_at, self.r().data.lock_duration); + let wait_maker_payment_until = wait_for_maker_payment_conf_until(self.r().data.started_at, lock_duration); let watcher_reward = if self.r().watcher_reward { match self .maker_coin @@ -841,20 +842,23 @@ impl MakerSwap { Ok(res) => match res { Some(tx) => tx, None => { - let payment_fut = self.maker_coin.send_maker_payment(SendPaymentArgs { - time_lock_duration: self.r().data.lock_duration, - time_lock: self.r().data.maker_payment_lock, - other_pubkey: &*self.r().other_maker_coin_htlc_pub, - secret_hash: secret_hash.as_slice(), - amount: self.maker_amount.clone(), - swap_contract_address: &self.r().data.maker_coin_swap_contract_address, - swap_unique_data: &unique_data, - payment_instructions: &self.r().payment_instructions, - watcher_reward, - wait_for_confirmation_until: wait_maker_payment_until, - }); - - match payment_fut.compat().await { + let payment = self + .maker_coin + .send_maker_payment(SendPaymentArgs { + time_lock_duration: lock_duration, + time_lock: maker_payment_lock, + other_pubkey: &*other_maker_coin_htlc_pub, + secret_hash: secret_hash.as_slice(), + amount: self.maker_amount.clone(), + swap_contract_address: &maker_coin_swap_contract_address, + swap_unique_data: &unique_data, + payment_instructions: &payment_instructions, + watcher_reward, + wait_for_confirmation_until: wait_maker_payment_until, + }) + .await; + + match payment { Ok(t) => t, Err(err) => { return Ok((Some(MakerSwapCommand::Finish), vec![ @@ -1141,17 +1145,17 @@ impl MakerSwap { } async fn confirm_taker_payment_spend(&self) -> Result<(Option, Vec), String> { - // we should wait for only one confirmation to make sure our spend transaction is not failed - let confirmations = std::cmp::min(1, self.r().data.taker_payment_confirmations); let requires_nota = false; - let confirm_taker_payment_input = ConfirmPaymentInput { - payment_tx: self.r().taker_payment.clone().unwrap().tx_hex.0, - confirmations, + let confirm_taker_payment_spend_input = ConfirmPaymentInput { + payment_tx: self.r().taker_payment_spend.clone().unwrap().tx_hex.0, + confirmations: self.r().data.taker_payment_confirmations, requires_nota, wait_until: self.wait_refund_until(), check_every: WAIT_CONFIRM_INTERVAL_SEC, }; - let wait_fut = self.taker_coin.wait_for_confirmations(confirm_taker_payment_input); + let wait_fut = self + .taker_coin + .wait_for_confirmations(confirm_taker_payment_spend_input); if let Err(err) = wait_fut.compat().await { return Ok((Some(MakerSwapCommand::PrepareForMakerPaymentRefund), vec![ MakerSwapEvent::TakerPaymentSpendConfirmFailed( @@ -1208,7 +1212,7 @@ impl MakerSwap { } loop { - match self.maker_coin.can_refund_htlc(locktime).compat().await { + match self.maker_coin.can_refund_htlc(locktime).await { Ok(CanRefundHtlc::CanRefundNow) => break, Ok(CanRefundHtlc::HaveToWait(to_sleep)) => Timer::sleep(to_sleep as f64).await, Err(e) => { @@ -1478,7 +1482,6 @@ impl MakerSwap { amount: &self.maker_amount, payment_instructions: &payment_instructions, }) - .compat() .await ); match maybe_maker_payment { @@ -1521,7 +1524,7 @@ impl MakerSwap { return ERR!("Maker payment will be refunded automatically!"); } - let can_refund_htlc = try_s!(self.maker_coin.can_refund_htlc(maker_payment_lock).compat().await); + let can_refund_htlc = try_s!(self.maker_coin.can_refund_htlc(maker_payment_lock).await); if let CanRefundHtlc::HaveToWait(seconds_to_wait) = can_refund_htlc { return ERR!("Too early to refund, wait until {}", wait_until_sec(seconds_to_wait)); } @@ -1677,7 +1680,7 @@ impl MakerSwapEvent { }, MakerSwapEvent::TakerPaymentSpent(_) => "Taker payment spent...".to_owned(), MakerSwapEvent::TakerPaymentSpendFailed(_) => "Taker payment spend failed...".to_owned(), - MakerSwapEvent::TakerPaymentSpendConfirmStarted => "Taker payment send wait confirm started...".to_owned(), + MakerSwapEvent::TakerPaymentSpendConfirmStarted => "Taker payment spend confirm started...".to_owned(), MakerSwapEvent::TakerPaymentSpendConfirmed => "Taker payment spend confirmed...".to_owned(), MakerSwapEvent::TakerPaymentSpendConfirmFailed(_) => "Taker payment spend confirm failed...".to_owned(), MakerSwapEvent::MakerPaymentWaitRefundStarted { wait_until } => { @@ -2392,12 +2395,12 @@ mod maker_swap_tests { TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); TestCoin::can_refund_htlc - .mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(CanRefundHtlc::CanRefundNow)))); + .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ok(CanRefundHtlc::CanRefundNow)))); static mut MY_PAYMENT_SENT_CALLED: bool = false; TestCoin::check_if_my_payment_sent.mock_safe(|_, _| { unsafe { MY_PAYMENT_SENT_CALLED = true }; - MockResult::Return(Box::new(futures01::future::ok(Some(eth_tx_for_test().into())))) + MockResult::Return(Box::pin(futures::future::ok(Some(eth_tx_for_test().into())))) }); static mut MAKER_REFUND_CALLED: bool = false; @@ -2432,7 +2435,7 @@ mod maker_swap_tests { TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); TestCoin::can_refund_htlc - .mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(CanRefundHtlc::CanRefundNow)))); + .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ok(CanRefundHtlc::CanRefundNow)))); static mut MAKER_REFUND_CALLED: bool = false; TestCoin::send_maker_refunds_payment.mock_safe(|_, _| { @@ -2527,10 +2530,10 @@ mod maker_swap_tests { static mut MY_PAYMENT_SENT_CALLED: bool = false; TestCoin::check_if_my_payment_sent.mock_safe(|_, _| { unsafe { MY_PAYMENT_SENT_CALLED = true }; - MockResult::Return(Box::new(futures01::future::ok(Some(eth_tx_for_test().into())))) + MockResult::Return(Box::pin(futures::future::ok(Some(eth_tx_for_test().into())))) }); TestCoin::can_refund_htlc - .mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(CanRefundHtlc::HaveToWait(1000))))); + .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ok(CanRefundHtlc::HaveToWait(1000))))); TestCoin::search_for_swap_tx_spend_my .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ready(Ok(None))))); let maker_coin = MmCoinEnum::Test(TestCoin::default()); @@ -2556,7 +2559,7 @@ mod maker_swap_tests { static mut MY_PAYMENT_SENT_CALLED: bool = false; TestCoin::check_if_my_payment_sent.mock_safe(|_, _| { unsafe { MY_PAYMENT_SENT_CALLED = true }; - MockResult::Return(Box::new(futures01::future::ok(None))) + MockResult::Return(Box::pin(futures::future::ok(None))) }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); @@ -2722,7 +2725,7 @@ mod maker_swap_tests { #[test] fn swap_must_not_lock_funds_by_default() { - use crate::mm2::lp_swap::get_locked_amount; + use crate::lp_swap::get_locked_amount; let ctx = mm_ctx_with_iguana(PASSPHRASE); diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index 6e9555c10f..d0e667a752 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -1,16 +1,16 @@ use super::swap_v2_common::*; use super::{swap_v2_topic, LockedAmount, LockedAmountInfo, SavedTradeFee, SwapsContext, NEGOTIATE_SEND_INTERVAL, NEGOTIATION_TIMEOUT_SEC}; -use crate::mm2::lp_swap::maker_swap::MakerSwapPreparedParams; -use crate::mm2::lp_swap::swap_lock::SwapLock; -use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_swap, recv_swap_v2_msg, SecretHashAlgo, - SwapConfirmationsSettings, TransactionIdentifier, MAKER_SWAP_V2_TYPE, MAX_STARTED_AT_DIFF}; -use crate::mm2::lp_swap::{swap_v2_pb::*, NO_REFUND_FEE}; +use crate::lp_swap::maker_swap::MakerSwapPreparedParams; +use crate::lp_swap::swap_lock::SwapLock; +use crate::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_swap, recv_swap_v2_msg, SecretHashAlgo, + SwapConfirmationsSettings, TransactionIdentifier, MAKER_SWAP_V2_TYPE, MAX_STARTED_AT_DIFF}; +use crate::lp_swap::{swap_v2_pb::*, NO_REFUND_FEE}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use coins::{CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, FundingTxSpend, GenTakerFundingSpendArgs, - GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, ParseCoinAssocTypes, RefundMakerPaymentArgs, - RefundPaymentArgs, SearchForFundingSpendErr, SendMakerPaymentArgs, SwapTxTypeWithSecretHash, + GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, ParseCoinAssocTypes, RefundMakerPaymentSecretArgs, + RefundMakerPaymentTimelockArgs, SearchForFundingSpendErr, SendMakerPaymentArgs, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, ToBytes, TradePreimageValue, Transaction, TxPreimageWithSig, ValidateTakerFundingArgs}; use common::executor::abortable_queue::AbortableQueue; use common::executor::{AbortableSystem, Timer}; @@ -32,14 +32,14 @@ use std::marker::PhantomData; use uuid::Uuid; cfg_native!( - use crate::mm2::database::my_swaps::{insert_new_swap_v2, SELECT_MY_SWAP_V2_BY_UUID}; + use crate::database::my_swaps::{insert_new_swap_v2, SELECT_MY_SWAP_V2_BY_UUID}; use common::async_blocking; use db_common::sqlite::rusqlite::{named_params, Error as SqlError, Result as SqlResult, Row}; use db_common::sqlite::rusqlite::types::Type as SqlType; ); cfg_wasm32!( - use crate::mm2::lp_swap::swap_wasm_db::{MySwapsFiltersTable, SavedSwapTable}; + use crate::lp_swap::swap_wasm_db::{MySwapsFiltersTable, SavedSwapTable}; ); // This is needed to have Debug on messages @@ -353,7 +353,8 @@ pub struct MakerSwapStateMachine { + | e @ SearchForFundingSpendErr::FromBlockConversionErr(_) + | e @ SearchForFundingSpendErr::Internal(_) => { let next_state = MakerPaymentRefundRequired { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, @@ -1446,7 +1451,7 @@ impl break, @@ -1490,21 +1495,21 @@ impl for MaxMakerVolRpcError { impl HttpStatusCode for MaxMakerVolRpcError { fn status_code(&self) -> StatusCode { match self { + MaxMakerVolRpcError::NoSuchCoin { .. } => StatusCode::NOT_FOUND, MaxMakerVolRpcError::NotSufficientBalance { .. } | MaxMakerVolRpcError::NotSufficientBaseCoinBalance { .. } - | MaxMakerVolRpcError::VolumeTooLow { .. } - | MaxMakerVolRpcError::NoSuchCoin { .. } - | MaxMakerVolRpcError::CoinIsWalletOnly { .. } => StatusCode::BAD_REQUEST, + | MaxMakerVolRpcError::VolumeTooLow { .. } => StatusCode::BAD_REQUEST, MaxMakerVolRpcError::Transport(_) | MaxMakerVolRpcError::InternalError(_) => { StatusCode::INTERNAL_SERVER_ERROR }, @@ -138,14 +135,12 @@ pub struct MaxMakerVolResponse { pub async fn max_maker_vol(ctx: MmArc, req: MaxMakerVolRequest) -> MmResult { let coin = lp_coinfind_or_err(&ctx, &req.coin).await?; - if coin.wallet_only(&ctx) { - return MmError::err(MaxMakerVolRpcError::CoinIsWalletOnly { coin: req.coin }); - } let CoinVolumeInfo { volume, balance, locked_by_swaps, } = get_max_maker_vol(&ctx, &coin).await?; + Ok(MaxMakerVolResponse { coin: req.coin, volume: MmNumberMultiRepr::from(volume), diff --git a/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs b/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs index 33640350aa..f725dce5dc 100644 --- a/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs +++ b/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs @@ -58,7 +58,7 @@ impl MySwapsStorage { #[cfg(not(target_arch = "wasm32"))] mod native_impl { use super::*; - use crate::mm2::database::my_swaps::{insert_new_swap, select_uuids_by_my_swaps_filter, SelectSwapsUuidsErr}; + use crate::database::my_swaps::{insert_new_swap, select_uuids_by_my_swaps_filter, SelectSwapsUuidsErr}; use db_common::sqlite::rusqlite::Error as SqlError; impl From for MySwapsError { @@ -111,9 +111,9 @@ mod native_impl { #[cfg(target_arch = "wasm32")] mod wasm_impl { use super::*; - use crate::mm2::lp_swap::swap_wasm_db::cursor_prelude::*; - use crate::mm2::lp_swap::swap_wasm_db::{DbTransactionError, InitDbError, MySwapsFiltersTable}; - use crate::mm2::lp_swap::{SwapsContext, LEGACY_SWAP_TYPE}; + use crate::lp_swap::swap_wasm_db::cursor_prelude::*; + use crate::lp_swap::swap_wasm_db::{DbTransactionError, InitDbError, MySwapsFiltersTable}; + use crate::lp_swap::{SwapsContext, LEGACY_SWAP_TYPE}; use std::collections::BTreeSet; use uuid::Uuid; @@ -328,7 +328,7 @@ mod wasm_impl { mod wasm_tests { use super::wasm_impl::*; use super::*; - use crate::mm2::lp_swap::{LEGACY_SWAP_TYPE, MAKER_SWAP_V2_TYPE, TAKER_SWAP_V2_TYPE}; + use crate::lp_swap::{LEGACY_SWAP_TYPE, MAKER_SWAP_V2_TYPE, TAKER_SWAP_V2_TYPE}; use common::log::wasm_log::register_wasm_log; use common::new_uuid; use mm2_core::mm_ctx::MmCtxBuilder; diff --git a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs index 56e7877b70..e4e430c71c 100644 --- a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs +++ b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs @@ -1,9 +1,9 @@ -use crate::mm2::lp_swap::maker_swap::{MakerSwapData, MakerSwapEvent, TakerNegotiationData, MAKER_ERROR_EVENTS, - MAKER_SUCCESS_EVENTS}; -use crate::mm2::lp_swap::taker_swap::{MakerNegotiationData, TakerPaymentSpentData, TakerSavedEvent, TakerSwapData, - TakerSwapEvent, TAKER_ERROR_EVENTS, TAKER_SUCCESS_EVENTS}; -use crate::mm2::lp_swap::{wait_for_maker_payment_conf_until, MakerSavedEvent, MakerSavedSwap, SavedSwap, SwapError, - TakerSavedSwap}; +use crate::lp_swap::maker_swap::{MakerSwapData, MakerSwapEvent, TakerNegotiationData, MAKER_ERROR_EVENTS, + MAKER_SUCCESS_EVENTS}; +use crate::lp_swap::taker_swap::{MakerNegotiationData, TakerPaymentSpentData, TakerSavedEvent, TakerSwapData, + TakerSwapEvent, TAKER_ERROR_EVENTS, TAKER_SUCCESS_EVENTS}; +use crate::lp_swap::{wait_for_maker_payment_conf_until, MakerSavedEvent, MakerSavedSwap, SavedSwap, SwapError, + TakerSavedSwap}; use coins::{lp_coinfind, MmCoinEnum}; use common::{HttpStatusCode, StatusCode}; use derive_more::Display; @@ -264,6 +264,8 @@ fn convert_taker_to_maker_events( | TakerSwapEvent::MakerPaymentWaitConfirmStarted | TakerSwapEvent::MakerPaymentValidatedAndConfirmed | TakerSwapEvent::MakerPaymentSpent(_) + | TakerSwapEvent::MakerPaymentSpendConfirmed + | TakerSwapEvent::MakerPaymentSpendConfirmFailed(_) | TakerSwapEvent::MakerPaymentSpentByWatcher(_) | TakerSwapEvent::MakerPaymentSpendFailed(_) // We don't know the reason at the moment, so we rely on the errors handling above. diff --git a/mm2src/mm2_main/src/lp_swap/saved_swap.rs b/mm2src/mm2_main/src/lp_swap/saved_swap.rs index 1531ceb28b..c471f7eacb 100644 --- a/mm2src/mm2_main/src/lp_swap/saved_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/saved_swap.rs @@ -1,6 +1,6 @@ -use crate::mm2::lp_swap::maker_swap::{MakerSavedSwap, MakerSwap, MakerSwapEvent}; -use crate::mm2::lp_swap::taker_swap::{TakerSavedSwap, TakerSwap, TakerSwapEvent}; -use crate::mm2::lp_swap::{MySwapInfo, RecoveredSwap}; +use crate::lp_swap::maker_swap::{MakerSavedSwap, MakerSwap, MakerSwapEvent}; +use crate::lp_swap::taker_swap::{TakerSavedSwap, TakerSwap, TakerSwapEvent}; +use crate::lp_swap::{MySwapInfo, RecoveredSwap}; use async_trait::async_trait; use coins::lp_coinfind; use derive_more::Display; @@ -184,9 +184,9 @@ pub trait SavedSwapIo { #[cfg(not(target_arch = "wasm32"))] mod native_impl { use super::*; - use crate::mm2::lp_swap::maker_swap::{stats_maker_swap_dir, stats_maker_swap_file_path}; - use crate::mm2::lp_swap::taker_swap::{stats_taker_swap_dir, stats_taker_swap_file_path}; - use crate::mm2::lp_swap::{my_swap_file_path, my_swaps_dir}; + use crate::lp_swap::maker_swap::{stats_maker_swap_dir, stats_maker_swap_file_path}; + use crate::lp_swap::taker_swap::{stats_taker_swap_dir, stats_taker_swap_file_path}; + use crate::lp_swap::{my_swap_file_path, my_swaps_dir}; use mm2_io::fs::{read_dir_json, read_json, write_json, FsJsonError}; const USE_TMP_FILE: bool = true; @@ -262,9 +262,9 @@ mod native_impl { #[cfg(target_arch = "wasm32")] mod wasm_impl { use super::*; - use crate::mm2::lp_swap::swap_wasm_db::{DbTransactionError, InitDbError, MySwapsFiltersTable, SavedSwapTable, - SwapsMigrationTable}; - use crate::mm2::lp_swap::{SwapsContext, LEGACY_SWAP_TYPE}; + use crate::lp_swap::swap_wasm_db::{DbTransactionError, InitDbError, MySwapsFiltersTable, SavedSwapTable, + SwapsMigrationTable}; + use crate::lp_swap::{SwapsContext, LEGACY_SWAP_TYPE}; use bytes::Buf; use common::log::{info, warn}; use mm2_db::indexed_db::cursor_prelude::CursorError; @@ -278,13 +278,13 @@ mod wasm_impl { .cursor_builder() .bound("migration", 0, u32::MAX) .reverse() + .where_first() .open_cursor("migration") .await? - // TODO refactor when "closure invoked recursively or after being dropped" is fixed - .collect() + .next() .await?; - Ok(migrations.first().map(|(_, m)| m.migration).unwrap_or_default()) + Ok(migrations.map(|(_, m)| m.migration).unwrap_or_default()) } pub async fn migrate_swaps_data(ctx: &MmArc) -> MmResult<(), SavedSwapError> { @@ -426,8 +426,8 @@ mod wasm_impl { #[cfg(target_arch = "wasm32")] mod tests { use super::*; - use crate::mm2::lp_swap::swap_wasm_db::{ItemId, MySwapsFiltersTable, SavedSwapTable, SwapsMigrationTable}; - use crate::mm2::lp_swap::{SwapsContext, LEGACY_SWAP_TYPE}; + use crate::lp_swap::swap_wasm_db::{ItemId, MySwapsFiltersTable, SavedSwapTable, SwapsMigrationTable}; + use crate::lp_swap::{SwapsContext, LEGACY_SWAP_TYPE}; use mm2_core::mm_ctx::MmCtxBuilder; use serde_json as json; use wasm_bindgen_test::*; diff --git a/mm2src/mm2_main/src/lp_swap/swap_lock.rs b/mm2src/mm2_main/src/lp_swap/swap_lock.rs index 25e752d14b..f4347dde3b 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_lock.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_lock.rs @@ -32,7 +32,7 @@ pub trait SwapLockOps: Sized { #[cfg(not(target_arch = "wasm32"))] mod native_lock { use super::*; - use crate::mm2::lp_swap::my_swaps_dir; + use crate::lp_swap::my_swaps_dir; use mm2_io::file_lock::{FileLock, FileLockError}; use std::path::PathBuf; @@ -70,8 +70,8 @@ mod native_lock { #[cfg(target_arch = "wasm32")] mod wasm_lock { use super::*; - use crate::mm2::lp_swap::swap_wasm_db::{DbTransactionError, InitDbError, ItemId, SwapLockTable}; - use crate::mm2::lp_swap::SwapsContext; + use crate::lp_swap::swap_wasm_db::{DbTransactionError, InitDbError, ItemId, SwapLockTable}; + use crate::lp_swap::SwapsContext; use common::executor::SpawnFuture; use common::log::{debug, error}; use common::{now_float, now_ms}; @@ -194,8 +194,8 @@ mod wasm_lock { mod tests { use super::wasm_lock::*; use super::*; - use crate::mm2::lp_swap::swap_wasm_db::SwapLockTable; - use crate::mm2::lp_swap::SwapsContext; + use crate::lp_swap::swap_wasm_db::SwapLockTable; + use crate::lp_swap::SwapsContext; use common::executor::Timer; use common::new_uuid; use common::now_ms; diff --git a/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs b/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs index ec87e9b79b..686d263d5a 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs @@ -1,6 +1,6 @@ -use crate::mm2::lp_network::{subscribe_to_topic, unsubscribe_from_topic}; -use crate::mm2::lp_swap::swap_lock::{SwapLock, SwapLockError, SwapLockOps}; -use crate::mm2::lp_swap::{swap_v2_topic, SwapsContext}; +use crate::lp_network::{subscribe_to_topic, unsubscribe_from_topic}; +use crate::lp_swap::swap_lock::{SwapLock, SwapLockError, SwapLockOps}; +use crate::lp_swap::{swap_v2_topic, SwapsContext}; use coins::utxo::utxo_standard::UtxoStandardCoin; use coins::{lp_coinfind, MmCoinEnum}; use common::executor::abortable_queue::AbortableQueue; @@ -18,13 +18,13 @@ use uuid::Uuid; cfg_native!( use common::async_blocking; - use crate::mm2::database::my_swaps::{does_swap_exist, get_swap_events, update_swap_events, - select_unfinished_swaps_uuids, set_swap_is_finished}; + use crate::database::my_swaps::{does_swap_exist, get_swap_events, update_swap_events, select_unfinished_swaps_uuids, + set_swap_is_finished}; ); cfg_wasm32!( use common::bool_as_int::BoolAsInt; - use crate::mm2::lp_swap::swap_wasm_db::{IS_FINISHED_SWAP_TYPE_INDEX, MySwapsFiltersTable, SavedSwapTable}; + use crate::lp_swap::swap_wasm_db::{IS_FINISHED_SWAP_TYPE_INDEX, MySwapsFiltersTable, SavedSwapTable}; use mm2_db::indexed_db::{DbTransactionError, InitDbError, MultiIndex}; ); diff --git a/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs b/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs index d5c50c6534..e3f57e8400 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs @@ -18,7 +18,7 @@ use std::num::NonZeroUsize; use uuid::Uuid; cfg_native!( - use crate::mm2::database::my_swaps::SELECT_MY_SWAP_V2_FOR_RPC_BY_UUID; + use crate::database::my_swaps::SELECT_MY_SWAP_V2_FOR_RPC_BY_UUID; use common::async_blocking; use db_common::sqlite::query_single_row; use db_common::sqlite::rusqlite::{Result as SqlResult, Row, Error as SqlError}; @@ -29,7 +29,7 @@ cfg_wasm32!( use super::SwapsContext; use super::maker_swap_v2::MakerSwapDbRepr; use super::taker_swap_v2::TakerSwapDbRepr; - use crate::mm2::lp_swap::swap_wasm_db::{MySwapsFiltersTable, SavedSwapTable}; + use crate::lp_swap::swap_wasm_db::{MySwapsFiltersTable, SavedSwapTable}; use mm2_db::indexed_db::{DbTransactionError, DbTransactionResult, InitDbError}; ); @@ -77,7 +77,7 @@ impl From for SwapV2DbError { #[cfg(target_arch = "wasm32")] pub(super) async fn get_swap_type(ctx: &MmArc, uuid: &Uuid) -> MmResult, SwapV2DbError> { - use crate::mm2::lp_swap::swap_wasm_db::MySwapsFiltersTable; + use crate::lp_swap::swap_wasm_db::MySwapsFiltersTable; let swaps_ctx = SwapsContext::from_ctx(ctx).unwrap(); let db = swaps_ctx.swap_db().await?; diff --git a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs index c88b34f76b..312f62e5c5 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs @@ -1,8 +1,9 @@ use super::{broadcast_p2p_tx_msg, get_payment_locktime, lp_coinfind, taker_payment_spend_deadline, tx_helper_topic, - H256Json, SwapsContext, WAIT_CONFIRM_INTERVAL_SEC}; -use crate::mm2::lp_network::{P2PRequestError, P2PRequestResult}; + H256Json, SwapsContext, TAKER_FEE_VALIDATION_ATTEMPTS, TAKER_FEE_VALIDATION_RETRY_DELAY_SECS, + WAIT_CONFIRM_INTERVAL_SEC}; +use crate::lp_network::{P2PRequestError, P2PRequestResult}; -use crate::mm2::MmError; +use crate::MmError; use async_trait::async_trait; use coins::{CanRefundHtlc, ConfirmPaymentInput, FoundSwapTxSpend, MmCoinEnum, RefundPaymentArgs, SendMakerPaymentSpendPreimageInput, SwapTxTypeWithSecretHash, WaitForHTLCTxSpendArgs, @@ -181,24 +182,31 @@ impl State for ValidateTakerFee { async fn on_changed(self: Box, watcher_ctx: &mut WatcherStateMachine) -> StateResult { debug!("Watcher validate taker fee"); - let validated_f = watcher_ctx - .taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: watcher_ctx.data.taker_fee_hash.clone(), - sender_pubkey: watcher_ctx.verified_pub.clone(), - min_block_number: watcher_ctx.data.taker_coin_start_block, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.clone(), - lock_duration: watcher_ctx.data.lock_duration, - }) - .compat(); - - if let Err(err) = validated_f.await { - return Self::change_state(Stopped::from_reason(StopReason::Error( - WatcherError::InvalidTakerFee(format!("{:?}", err)).into(), - ))); - }; - Self::change_state(ValidateTakerPayment {}) + let validation_result = retry_on_err!(async { + watcher_ctx + .taker_coin + .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: watcher_ctx.data.taker_fee_hash.clone(), + sender_pubkey: watcher_ctx.verified_pub.clone(), + min_block_number: watcher_ctx.data.taker_coin_start_block, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.clone(), + lock_duration: watcher_ctx.data.lock_duration, + }) + .compat() + .await + }) + .repeat_every_secs(TAKER_FEE_VALIDATION_RETRY_DELAY_SECS) + .attempts(TAKER_FEE_VALIDATION_ATTEMPTS) + .inspect_err(|e| error!("Error validating taker fee: {}", e)) + .await; + + match validation_result { + Ok(_) => Self::change_state(ValidateTakerPayment {}), + Err(repeat_err) => Self::change_state(Stopped::from_reason(StopReason::Error( + WatcherError::InvalidTakerFee(repeat_err.to_string()).into(), + ))), + } } } @@ -346,17 +354,20 @@ impl State for WaitForTakerPaymentSpend { }, }; - let f = watcher_ctx.maker_coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &maker_payment_hex, - secret_hash: &watcher_ctx.data.secret_hash, - wait_until, - from_block: watcher_ctx.data.maker_coin_start_block, - swap_contract_address: &None, - check_every: payment_search_interval, - watcher_reward: watcher_ctx.watcher_reward, - }); - - if f.compat().await.is_ok() { + if watcher_ctx + .maker_coin + .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &maker_payment_hex, + secret_hash: &watcher_ctx.data.secret_hash, + wait_until, + from_block: watcher_ctx.data.maker_coin_start_block, + swap_contract_address: &None, + check_every: payment_search_interval, + watcher_reward: watcher_ctx.watcher_reward, + }) + .await + .is_ok() + { info!("{}", MAKER_PAYMENT_SPEND_FOUND_LOG); return Self::change_state(Stopped::from_reason(StopReason::Finished( WatcherSuccess::MakerPaymentSpentByTaker, @@ -445,7 +456,6 @@ impl State for RefundTakerPayment { match watcher_ctx .taker_coin .can_refund_htlc(watcher_ctx.taker_locktime()) - .compat() .await { Ok(CanRefundHtlc::CanRefundNow) => break, diff --git a/mm2src/mm2_main/src/lp_swap/taker_restart.rs b/mm2src/mm2_main/src/lp_swap/taker_restart.rs index 8d4d5a2bc5..d934b6b11e 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_restart.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_restart.rs @@ -1,7 +1,7 @@ use super::taker_swap::TakerSwapCommand; use super::{AtomicSwap, TakerSavedSwap, TakerSwap}; -use crate::mm2::lp_swap::taker_swap::{TakerPaymentSpentData, TakerSavedEvent, TakerSwapEvent}; -use crate::mm2::lp_swap::{SavedSwap, SavedSwapIo, TransactionIdentifier, MAKER_PAYMENT_SPENT_BY_WATCHER_LOG}; +use crate::lp_swap::taker_swap::{TakerPaymentSpentData, TakerSavedEvent, TakerSwapEvent}; +use crate::lp_swap::{SavedSwap, SavedSwapIo, TransactionIdentifier, MAKER_PAYMENT_SPENT_BY_WATCHER_LOG}; use coins::{FoundSwapTxSpend, SearchForSwapTxSpendInput, TransactionEnum, ValidateWatcherSpendInput, WatcherSpendType}; use common::log::info; use common::{now_ms, Future01CompatExt}; @@ -49,6 +49,7 @@ pub async fn get_command_based_on_maker_or_watcher_activity( Err(e) => ERR!("Error {} when trying to find taker payment spend", e), }, TakerSwapCommand::SpendMakerPayment => check_maker_payment_spend_and_add_event(ctx, swap, saved).await, + TakerSwapCommand::ConfirmMakerPaymentSpend => Ok(command), TakerSwapCommand::PrepareForTakerPaymentRefund | TakerSwapCommand::RefundTakerPayment => { #[cfg(not(any(test, feature = "run-docker-tests")))] { @@ -141,7 +142,7 @@ pub async fn check_maker_payment_spend_and_add_event( let new_swap = SavedSwap::Taker(saved); try_s!(new_swap.save_to_db(ctx).await); info!("{}", MAKER_PAYMENT_SPENT_BY_WATCHER_LOG); - Ok(TakerSwapCommand::Finish) + Ok(TakerSwapCommand::ConfirmMakerPaymentSpend) } pub async fn check_taker_payment_spend(swap: &TakerSwap) -> Result, String> { diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 4e087ad527..c7b1cf59a9 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -10,12 +10,12 @@ use super::{broadcast_my_swap_status, broadcast_swap_message, broadcast_swap_msg NegotiationDataMsg, NegotiationDataV2, NegotiationDataV3, RecoveredSwap, RecoveredSwapAction, SavedSwap, SavedSwapIo, SavedTradeFee, SwapConfirmationsSettings, SwapError, SwapMsg, SwapPubkeys, SwapTxDataMsg, SwapsContext, TransactionIdentifier, INCLUDE_REFUND_FEE, NO_REFUND_FEE, WAIT_CONFIRM_INTERVAL_SEC}; -use crate::mm2::lp_network::subscribe_to_topic; -use crate::mm2::lp_ordermatch::TakerOrderBuilder; -use crate::mm2::lp_swap::swap_v2_common::mark_swap_as_finished; -use crate::mm2::lp_swap::taker_restart::get_command_based_on_maker_or_watcher_activity; -use crate::mm2::lp_swap::{broadcast_p2p_tx_msg, broadcast_swap_msg_every_delayed, tx_helper_topic, - wait_for_maker_payment_conf_duration, TakerSwapWatcherData, MAX_STARTED_AT_DIFF}; +use crate::lp_network::subscribe_to_topic; +use crate::lp_ordermatch::TakerOrderBuilder; +use crate::lp_swap::swap_v2_common::mark_swap_as_finished; +use crate::lp_swap::taker_restart::get_command_based_on_maker_or_watcher_activity; +use crate::lp_swap::{broadcast_p2p_tx_msg, broadcast_swap_msg_every_delayed, tx_helper_topic, + wait_for_maker_payment_conf_duration, TakerSwapWatcherData, MAX_STARTED_AT_DIFF}; use coins::lp_price::fetch_swap_coins_price; use coins::{lp_coinfind, CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MmCoin, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, @@ -44,7 +44,7 @@ use uuid::Uuid; const TAKER_PAYMENT_SPEND_SEARCH_INTERVAL: f64 = 10.; -pub const TAKER_SUCCESS_EVENTS: [&str; 11] = [ +pub const TAKER_SUCCESS_EVENTS: [&str; 12] = [ "Started", "Negotiated", "TakerFeeSent", @@ -55,10 +55,11 @@ pub const TAKER_SUCCESS_EVENTS: [&str; 11] = [ "TakerPaymentSent", "TakerPaymentSpent", "MakerPaymentSpent", + "MakerPaymentSpendConfirmed", "Finished", ]; -pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 13] = [ +pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 14] = [ "Started", "Negotiated", "TakerFeeSent", @@ -71,10 +72,11 @@ pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 13] = [ "TakerPaymentSpent", "MakerPaymentSpent", "MakerPaymentSpentByWatcher", + "MakerPaymentSpendConfirmed", "Finished", ]; -pub const TAKER_ERROR_EVENTS: [&str; 16] = [ +pub const TAKER_ERROR_EVENTS: [&str; 17] = [ "StartFailed", "NegotiateFailed", "TakerFeeSendFailed", @@ -85,6 +87,7 @@ pub const TAKER_ERROR_EVENTS: [&str; 16] = [ "TakerPaymentDataSendFailed", "TakerPaymentWaitForSpendFailed", "MakerPaymentSpendFailed", + "MakerPaymentSpendConfirmFailed", "TakerPaymentWaitRefundStarted", "TakerPaymentRefundStarted", "TakerPaymentRefunded", @@ -178,8 +181,10 @@ impl TakerSavedEvent { TakerSwapEvent::TakerPaymentSpent(_) => Some(TakerSwapCommand::SpendMakerPayment), TakerSwapEvent::TakerPaymentWaitForSpendFailed(_) => Some(TakerSwapCommand::PrepareForTakerPaymentRefund), TakerSwapEvent::TakerPaymentWaitConfirmFailed(_) => Some(TakerSwapCommand::PrepareForTakerPaymentRefund), - TakerSwapEvent::MakerPaymentSpent(_) => Some(TakerSwapCommand::Finish), - TakerSwapEvent::MakerPaymentSpentByWatcher(_) => Some(TakerSwapCommand::Finish), + TakerSwapEvent::MakerPaymentSpent(_) => Some(TakerSwapCommand::ConfirmMakerPaymentSpend), + TakerSwapEvent::MakerPaymentSpendConfirmed => Some(TakerSwapCommand::Finish), + TakerSwapEvent::MakerPaymentSpendConfirmFailed(_) => Some(TakerSwapCommand::PrepareForTakerPaymentRefund), + TakerSwapEvent::MakerPaymentSpentByWatcher(_) => Some(TakerSwapCommand::ConfirmMakerPaymentSpend), TakerSwapEvent::MakerPaymentSpendFailed(_) => Some(TakerSwapCommand::PrepareForTakerPaymentRefund), TakerSwapEvent::TakerPaymentWaitRefundStarted { .. } => { Some(TakerSwapCommand::PrepareForTakerPaymentRefund) @@ -259,6 +264,9 @@ impl TakerSavedSwap { if !self.is_finished() { return false; }; + let mut maker_payment_spent = false; + let mut maker_payment_spent_by_watcher = false; + let mut maker_payment_spend_confirmed_failed = false; for event in self.events.iter() { match event.event { TakerSwapEvent::StartFailed(_) @@ -267,15 +275,26 @@ impl TakerSavedSwap { | TakerSwapEvent::MakerPaymentValidateFailed(_) | TakerSwapEvent::TakerPaymentRefunded(_) | TakerSwapEvent::TakerPaymentRefundedByWatcher(_) - | TakerSwapEvent::MakerPaymentSpent(_) - | TakerSwapEvent::MakerPaymentSpentByWatcher(_) + | TakerSwapEvent::MakerPaymentSpendConfirmed | TakerSwapEvent::MakerPaymentWaitConfirmFailed(_) => { return false; }, + TakerSwapEvent::MakerPaymentSpent(_) => { + maker_payment_spent = true; + }, + TakerSwapEvent::MakerPaymentSpentByWatcher(_) => { + maker_payment_spent_by_watcher = true; + }, + TakerSwapEvent::MakerPaymentSpendConfirmFailed(_) => { + maker_payment_spend_confirmed_failed = true; + }, _ => (), } } - true + // MakerPaymentSpent or MakerPaymentSpentByWatcher were the last success events but a new step `MakerPaymentSpendConfirmed` was added after them. + // For backward compatibility (old saved swaps) we need to check for MakerPaymentSpent or MakerPaymentSpentByWatcher + // and if there is no MakerPaymentSpendConfirmFailed. + maker_payment_spend_confirmed_failed || (!maker_payment_spent && !maker_payment_spent_by_watcher) } pub fn swap_data(&self) -> Result<&TakerSwapData, String> { @@ -554,6 +573,7 @@ pub struct TakerSwapMut { pub maker_payment: Option, pub taker_payment: Option, maker_payment_spend: Option, + maker_payment_spend_confirmed: bool, taker_payment_spend: Option, maker_payment_spend_preimage: Option>, taker_payment_refund_preimage: Option>, @@ -660,6 +680,8 @@ pub enum TakerSwapEvent { TakerPaymentSpent(TakerPaymentSpentData), TakerPaymentWaitForSpendFailed(SwapError), MakerPaymentSpent(TransactionIdentifier), + MakerPaymentSpendConfirmed, + MakerPaymentSpendConfirmFailed(SwapError), MakerPaymentSpentByWatcher(TransactionIdentifier), MakerPaymentSpendFailed(SwapError), TakerPaymentWaitRefundStarted { wait_until: u64 }, @@ -698,6 +720,8 @@ impl TakerSwapEvent { TakerSwapEvent::TakerPaymentSpent(_) => "Taker payment spent...".to_owned(), TakerSwapEvent::TakerPaymentWaitForSpendFailed(_) => "Taker payment wait for spend failed...".to_owned(), TakerSwapEvent::MakerPaymentSpent(_) => "Maker payment spent...".to_owned(), + TakerSwapEvent::MakerPaymentSpendConfirmed => "Maker payment spent confirmed...".to_owned(), + TakerSwapEvent::MakerPaymentSpendConfirmFailed(_) => "Maker payment spend confirm failed...".to_owned(), TakerSwapEvent::MakerPaymentSpentByWatcher(_) => "Maker payment spent by watcher...".to_owned(), TakerSwapEvent::MakerPaymentSpendFailed(_) => "Maker payment spend failed...".to_owned(), TakerSwapEvent::TakerPaymentWaitRefundStarted { wait_until } => { @@ -733,6 +757,7 @@ impl TakerSwapEvent { | TakerSwapEvent::TakerPaymentSent(_) | TakerSwapEvent::TakerPaymentSpent(_) | TakerSwapEvent::MakerPaymentSpent(_) + | TakerSwapEvent::MakerPaymentSpendConfirmed | TakerSwapEvent::MakerPaymentSpentByWatcher(_) | TakerSwapEvent::Finished ) @@ -751,6 +776,7 @@ pub enum TakerSwapCommand { SendTakerPayment, WaitForTakerPaymentSpend, SpendMakerPayment, + ConfirmMakerPaymentSpend, PrepareForTakerPaymentRefund, RefundTakerPayment, FinalizeTakerPaymentRefund, @@ -837,6 +863,8 @@ impl TakerSwap { }, TakerSwapEvent::TakerPaymentWaitForSpendFailed(err) => self.errors.lock().push(err), TakerSwapEvent::MakerPaymentSpent(tx) => self.w().maker_payment_spend = Some(tx), + TakerSwapEvent::MakerPaymentSpendConfirmed => self.w().maker_payment_spend_confirmed = true, + TakerSwapEvent::MakerPaymentSpendConfirmFailed(err) => self.errors.lock().push(err), TakerSwapEvent::MakerPaymentSpentByWatcher(tx) => self.w().maker_payment_spend = Some(tx), TakerSwapEvent::MakerPaymentSpendFailed(err) => self.errors.lock().push(err), TakerSwapEvent::TakerPaymentWaitRefundStarted { .. } => (), @@ -862,6 +890,7 @@ impl TakerSwap { TakerSwapCommand::SendTakerPayment => self.send_taker_payment().await, TakerSwapCommand::WaitForTakerPaymentSpend => self.wait_for_taker_payment_spend().await, TakerSwapCommand::SpendMakerPayment => self.spend_maker_payment().await, + TakerSwapCommand::ConfirmMakerPaymentSpend => self.confirm_maker_payment_spend().await, TakerSwapCommand::PrepareForTakerPaymentRefund => self.prepare_for_taker_payment_refund().await, TakerSwapCommand::RefundTakerPayment => self.refund_taker_payment().await, TakerSwapCommand::FinalizeTakerPaymentRefund => self.finalize_taker_payment_refund().await, @@ -907,6 +936,7 @@ impl TakerSwap { other_taker_coin_htlc_pub: H264::default(), taker_fee: None, maker_payment: None, + maker_payment_spend_confirmed: false, taker_payment: None, taker_payment_spend: None, maker_payment_spend_preimage: None, @@ -1277,7 +1307,6 @@ impl TakerSwap { let fee_tx = self .taker_coin .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount, self.uuid.as_bytes(), expire_at) - .compat() .await; let transaction = match fee_tx { Ok(t) => t, @@ -1493,20 +1522,26 @@ impl TakerSwap { ])); } + let taker_payment_lock = self.r().data.taker_payment_lock; + let other_taker_coin_htlc_pub = self.r().other_taker_coin_htlc_pub; + let secret_hash = self.r().secret_hash.clone(); + let taker_coin_swap_contract_address = self.r().data.taker_coin_swap_contract_address.clone(); let unique_data = self.unique_swap_data(); + let taker_amount_decimal = self.taker_amount.to_decimal(); + let payment_instructions = self.r().payment_instructions.clone(); let f = self.taker_coin.check_if_my_payment_sent(CheckIfMyPaymentSentArgs { - time_lock: self.r().data.taker_payment_lock, - other_pub: self.r().other_taker_coin_htlc_pub.as_slice(), - secret_hash: &self.r().secret_hash.0, + time_lock: taker_payment_lock, + other_pub: other_taker_coin_htlc_pub.as_slice(), + secret_hash: &secret_hash.0, search_from_block: self.r().data.taker_coin_start_block, - swap_contract_address: &self.r().data.taker_coin_swap_contract_address, + swap_contract_address: &taker_coin_swap_contract_address, swap_unique_data: &unique_data, - amount: &self.taker_amount.to_decimal(), - payment_instructions: &self.r().payment_instructions, + amount: &taker_amount_decimal, + payment_instructions: &payment_instructions, }); let reward_amount = self.r().reward_amount.clone(); - let wait_until = self.r().data.taker_payment_lock; + let wait_until = taker_payment_lock; let watcher_reward = if self.r().watcher_reward { match self .taker_coin @@ -1532,28 +1567,32 @@ impl TakerSwap { None }; - let transaction = match f.compat().await { + let transaction = match f.await { Ok(res) => match res { Some(tx) => tx, None => { let time_lock = match std::env::var("USE_TEST_LOCKTIME") { Ok(_) => self.r().data.started_at, - Err(_) => self.r().data.taker_payment_lock, + Err(_) => taker_payment_lock, }; - let payment_fut = self.taker_coin.send_taker_payment(SendPaymentArgs { - time_lock_duration: self.r().data.lock_duration, - time_lock, - other_pubkey: &*self.r().other_taker_coin_htlc_pub, - secret_hash: &self.r().secret_hash.0, - amount: self.taker_amount.to_decimal(), - swap_contract_address: &self.r().data.taker_coin_swap_contract_address, - swap_unique_data: &unique_data, - payment_instructions: &self.r().payment_instructions, - watcher_reward, - wait_for_confirmation_until: self.r().data.taker_payment_lock, - }); + let lock_duration = self.r().data.lock_duration; + let payment = self + .taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration: lock_duration, + time_lock, + other_pubkey: &*other_taker_coin_htlc_pub, + secret_hash: &secret_hash.0, + amount: taker_amount_decimal, + swap_contract_address: &taker_coin_swap_contract_address, + swap_unique_data: &unique_data, + payment_instructions: &payment_instructions, + watcher_reward, + wait_for_confirmation_until: taker_payment_lock, + }) + .await; - match payment_fut.compat().await { + match payment { Ok(t) => t, Err(err) => { return Ok((Some(TakerSwapCommand::Finish), vec![ @@ -1646,7 +1685,7 @@ impl TakerSwap { async fn wait_for_taker_payment_spend(&self) -> Result<(Option, Vec), String> { const BROADCAST_MSG_INTERVAL_SEC: f64 = 600.; - let tx_hex = self.r().taker_payment.as_ref().unwrap().tx_hex.0.clone(); + let tx_hex = self.r().taker_payment.as_ref().unwrap().tx_hex.clone(); let mut watcher_broadcast_abort_handle = None; // Watchers cannot be used for lightning swaps for now // Todo: Check if watchers can work in some cases with lightning and implement it if it's possible, this part will probably work if only the taker is lightning since the preimage is available @@ -1676,7 +1715,7 @@ impl TakerSwap { } // Todo: taker_payment should be a message on lightning network not a swap message - let msg = SwapMsg::TakerPayment(tx_hex); + let msg = SwapMsg::TakerPayment(tx_hex.0.clone()); let send_abort_handle = broadcast_swap_msg_every( self.ctx.clone(), swap_topic(&self.uuid), @@ -1685,50 +1724,34 @@ impl TakerSwap { self.p2p_privkey, ); - let confirm_taker_payment_input = ConfirmPaymentInput { - payment_tx: self.r().taker_payment.clone().unwrap().tx_hex.0, - confirmations: self.r().data.taker_payment_confirmations, - requires_nota: self.r().data.taker_payment_requires_nota.unwrap_or(false), - wait_until: self.r().data.taker_payment_lock, - check_every: WAIT_CONFIRM_INTERVAL_SEC, - }; - let wait_f = self - .taker_coin - .wait_for_confirmations(confirm_taker_payment_input) - .compat(); - if let Err(err) = wait_f.await { - return Ok((Some(TakerSwapCommand::PrepareForTakerPaymentRefund), vec![ - TakerSwapEvent::TakerPaymentWaitConfirmFailed( - ERRL!("!taker_coin.wait_for_confirmations: {}", err).into(), - ), - TakerSwapEvent::TakerPaymentWaitRefundStarted { - wait_until: self.wait_refund_until(), - }, - ])); - } - #[cfg(any(test, feature = "run-docker-tests"))] if self.fail_at == Some(FailAt::WaitForTakerPaymentSpendPanic) { + // Wait for 5 seconds before panicking to ensure the message is sent + Timer::sleep(5.).await; panic!("Taker panicked unexpectedly at wait for taker payment spend"); } - info!("Taker payment confirmed"); + info!("Waiting for maker to spend taker payment!"); let wait_until = match std::env::var("USE_TEST_LOCKTIME") { Ok(_) => self.r().data.started_at, Err(_) => self.r().data.taker_payment_lock, }; + let secret_hash = self.r().secret_hash.clone(); + let taker_coin_start_block = self.r().data.taker_coin_start_block; + let taker_coin_swap_contract_address = self.r().data.taker_coin_swap_contract_address.clone(); + let watcher_reward = self.r().watcher_reward; let f = self.taker_coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &self.r().taker_payment.clone().unwrap().tx_hex, - secret_hash: &self.r().secret_hash.0, + tx_bytes: &tx_hex, + secret_hash: &secret_hash.0, wait_until, - from_block: self.r().data.taker_coin_start_block, - swap_contract_address: &self.r().data.taker_coin_swap_contract_address, + from_block: taker_coin_start_block, + swap_contract_address: &taker_coin_swap_contract_address, check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: self.r().watcher_reward, + watcher_reward, }); - let tx = match f.compat().await { + let tx = match f.await { Ok(t) => t, Err(err) => { return Ok((Some(TakerSwapCommand::PrepareForTakerPaymentRefund), vec![ @@ -1748,8 +1771,6 @@ impl TakerSwap { tx_hash, }; - let secret_hash = self.r().secret_hash.clone(); - let watcher_reward = self.r().watcher_reward; let secret = match self .taker_coin .extract_secret(&secret_hash.0, &tx_ident.tx_hex, watcher_reward) @@ -1829,9 +1850,36 @@ impl TakerSwap { tx_hash, }; - Ok((Some(TakerSwapCommand::Finish), vec![TakerSwapEvent::MakerPaymentSpent( - tx_ident, - )])) + Ok((Some(TakerSwapCommand::ConfirmMakerPaymentSpend), vec![ + TakerSwapEvent::MakerPaymentSpent(tx_ident), + ])) + } + + async fn confirm_maker_payment_spend(&self) -> Result<(Option, Vec), String> { + let confirm_maker_payment_spend_input = ConfirmPaymentInput { + payment_tx: self.r().maker_payment_spend.clone().unwrap().tx_hex.0, + confirmations: self.r().data.maker_payment_confirmations, + requires_nota: false, + wait_until: self.wait_refund_until(), + check_every: WAIT_CONFIRM_INTERVAL_SEC, + }; + let wait_fut = self + .maker_coin + .wait_for_confirmations(confirm_maker_payment_spend_input); + if let Err(err) = wait_fut.compat().await { + return Ok((Some(TakerSwapCommand::PrepareForTakerPaymentRefund), vec![ + TakerSwapEvent::MakerPaymentSpendConfirmFailed( + ERRL!("!wait for maker payment spend confirmations: {}", err).into(), + ), + TakerSwapEvent::TakerPaymentWaitRefundStarted { + wait_until: self.wait_refund_until(), + }, + ])); + } + info!("Maker payment spend confirmed"); + Ok((Some(TakerSwapCommand::Finish), vec![ + TakerSwapEvent::MakerPaymentSpendConfirmed, + ])) } async fn prepare_for_taker_payment_refund( @@ -1873,7 +1921,7 @@ impl TakerSwap { } loop { - match self.taker_coin.can_refund_htlc(locktime).compat().await { + match self.taker_coin.can_refund_htlc(locktime).await { Ok(CanRefundHtlc::CanRefundNow) => break, Ok(CanRefundHtlc::HaveToWait(to_sleep)) => Timer::sleep(to_sleep as f64).await, Err(e) => { @@ -2057,8 +2105,8 @@ impl TakerSwap { return ERR!("Taker payment is refunded, swap is not recoverable"); } - if self.r().maker_payment_spend.is_some() { - return ERR!("Maker payment is spent, swap is not recoverable"); + if self.r().maker_payment_spend.is_some() && self.r().maker_payment_spend_confirmed { + return ERR!("Maker payment is spent and confirmed, swap is not recoverable"); } let maker_payment = match &self.r().maker_payment { @@ -2133,7 +2181,6 @@ impl TakerSwap { amount: &self.taker_amount.to_decimal(), payment_instructions: &payment_instructions, }) - .compat() .await ); match maybe_sent { @@ -2260,7 +2307,7 @@ impl TakerSwap { return ERR!("Taker payment will be refunded automatically!"); } - let can_refund = try_s!(self.taker_coin.can_refund_htlc(taker_payment_lock).compat().await); + let can_refund = try_s!(self.taker_coin.can_refund_htlc(taker_payment_lock).await); if let CanRefundHtlc::HaveToWait(seconds_to_wait) = can_refund { return ERR!("Too early to refund, wait until {}", wait_until_sec(seconds_to_wait)); } @@ -2676,7 +2723,7 @@ pub fn max_taker_vol_from_available( #[cfg(all(test, not(target_arch = "wasm32")))] mod taker_swap_tests { use super::*; - use crate::mm2::lp_swap::{dex_fee_amount, get_locked_amount_by_other_swaps}; + use crate::lp_swap::{dex_fee_amount, get_locked_amount_by_other_swaps}; use coins::eth::{addr_from_str, signed_eth_tx_from_bytes, SignedEthTx}; use coins::utxo::UtxoTx; use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoin, SwapOps, TestCoin}; @@ -2708,7 +2755,7 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_maker_payment_spend_errored() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1564050693585},{"event":{"data":{"tx_hash":"539cb6dbdc25465bbccc575554f05d1bb04c70efce4316e41194e747375c3659","tx_hex":"0100000001ffc8a8a1b43b4dceed0f8b7dcc2f72fdda92d52f32d25cc21c6d2d498b82debd010000006a47304402203967b7f9f5532fa47116585c7d1bcba51861ea2059cca00409f34660db18e33a0220640991911852533a12fdfeb039fb9c8ca2c45482c6993bd84636af3670d49c1501210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff0200f2052a0100000017a914f2fa08ae416b576779ae5da975e5442663215fce87415173f9000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac0585395d"},"type":"TakerPaymentSent"},"timestamp":1564050695611},{"event":{"data":{"secret":"1b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093","transaction":{"tx_hash":"cc5af1cf68d246419fee49c3d74c0cd173599d115b86efe274368a614951bc47","tx_hex":"010000000159365c3747e79411e41643ceef704cb01b5df0545557ccbc5b4625dcdbb69c5300000000d747304402200e78e27d2f1c18676f98ca3dfa4e4a9eeaa8209b55f57b4dd5d9e1abdf034cfa0220623b5c22b62234cec230342aa306c497e43494b44ec2425b84e236b1bf01257001201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b6304a7a2395db175210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a88821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68ffffffff01008d380c010000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8c77395d"}},"type":"TakerPaymentSpent"},"timestamp":1564051092890},{"event":{"data":{"error":"lp_swap:1981] utxo:891] rpc_clients:738] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"67\", method: \"blockchain.transaction.broadcast\", params: [String(\"0400008085202f890182b342c114f806c5325f23f7e78dae5d186221ab502c86302c2c8082fa110f0a00000000d7473044022035791ea5548f87484065c9e1f0bdca9ebc699f2c7f51182c84f360102e32dc3d02200612ed53bca52d9c2568437f087598531534badf26229fe0f652ea72ddf03ca501201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b630420c1395db17521031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a888210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac68ffffffff01460ec000000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac967e395d000000000000000000000000000000\")] }, error: Transport(\"rpc_clients:668] All electrums are currently disconnected\") }"},"type":"MakerPaymentSpendFailed"},"timestamp":1564051092897},{"event":{"type":"Finished"},"timestamp":1564051092900}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1564050693585},{"event":{"data":{"tx_hash":"539cb6dbdc25465bbccc575554f05d1bb04c70efce4316e41194e747375c3659","tx_hex":"0100000001ffc8a8a1b43b4dceed0f8b7dcc2f72fdda92d52f32d25cc21c6d2d498b82debd010000006a47304402203967b7f9f5532fa47116585c7d1bcba51861ea2059cca00409f34660db18e33a0220640991911852533a12fdfeb039fb9c8ca2c45482c6993bd84636af3670d49c1501210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff0200f2052a0100000017a914f2fa08ae416b576779ae5da975e5442663215fce87415173f9000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac0585395d"},"type":"TakerPaymentSent"},"timestamp":1564050695611},{"event":{"data":{"secret":"1b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093","transaction":{"tx_hash":"cc5af1cf68d246419fee49c3d74c0cd173599d115b86efe274368a614951bc47","tx_hex":"010000000159365c3747e79411e41643ceef704cb01b5df0545557ccbc5b4625dcdbb69c5300000000d747304402200e78e27d2f1c18676f98ca3dfa4e4a9eeaa8209b55f57b4dd5d9e1abdf034cfa0220623b5c22b62234cec230342aa306c497e43494b44ec2425b84e236b1bf01257001201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b6304a7a2395db175210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a88821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68ffffffff01008d380c010000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8c77395d"}},"type":"TakerPaymentSpent"},"timestamp":1564051092890},{"event":{"data":{"error":"lp_swap:1981] utxo:891] rpc_clients:738] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"67\", method: \"blockchain.transaction.broadcast\", params: [String(\"0400008085202f890182b342c114f806c5325f23f7e78dae5d186221ab502c86302c2c8082fa110f0a00000000d7473044022035791ea5548f87484065c9e1f0bdca9ebc699f2c7f51182c84f360102e32dc3d02200612ed53bca52d9c2568437f087598531534badf26229fe0f652ea72ddf03ca501201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b630420c1395db17521031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a888210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac68ffffffff01460ec000000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac967e395d000000000000000000000000000000\")] }, error: Transport(\"rpc_clients:668] All electrums are currently disconnected\") }"},"type":"MakerPaymentSpendFailed"},"timestamp":1564051092897},{"event":{"type":"Finished"},"timestamp":1564051092900}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -2744,18 +2791,18 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_errored_but_sent_not_spent() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_wait":1563746537,"my_persistent_pub":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","started_at":1563743937,"taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"taker_payment_lock":1563751737,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743937741},{"event":{"data":{"maker_payment_locktime":1563759539,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"432c8272ac59b47dea2d299b5cf1ee64ea1917b9"},"type":"Negotiated"},"timestamp":1563744003530},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeSent"},"timestamp":1563744020598},{"event":{"data":{"tx_hash":"0cf4acbcefde53645851c5c6053ea61fe0cbb5f828a906d69eb809e0b071a03b","tx_hex":"0400008085202f89025d5ae3e8c87418c9b735f8f2f7d29e26820c33c9f30d53f2d31f8b99ea9b1490010000006a47304402201185c06ca575261c539b287175751b7de642eb7466c59128639a19b4c2dd2f9b02201c8c4167d581864bedd4d1deb5596472e6e3ce29fe9e7996907a7b59c905d5490121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff06dbf9971c8dfd4a0c8c49f4f15c51de59ba13b2efa702682e26869843af9a87000000006a473044022012b47c12c7f6ad7d8b778fc4b5dcfd56a39325daf302f56e7b84753ba5216cfa022076bf571cf9e20facf70d2f134e8ed2de67aa08581a27ff3128bf93a9b594ac770121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff02fed727150000000017a914d5268b31131a652f9b6ddf57db62f02285cdfad1874e1d7835000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac37cf345d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563744071778},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563744071781},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563744118073},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"TakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563744118580}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_wait":1563746537,"my_persistent_pub":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","started_at":1563743937,"taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"taker_payment_lock":1563751737,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743937741},{"event":{"data":{"maker_payment_locktime":1563759539,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"432c8272ac59b47dea2d299b5cf1ee64ea1917b9"},"type":"Negotiated"},"timestamp":1563744003530},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeSent"},"timestamp":1563744020598},{"event":{"data":{"tx_hash":"0cf4acbcefde53645851c5c6053ea61fe0cbb5f828a906d69eb809e0b071a03b","tx_hex":"0400008085202f89025d5ae3e8c87418c9b735f8f2f7d29e26820c33c9f30d53f2d31f8b99ea9b1490010000006a47304402201185c06ca575261c539b287175751b7de642eb7466c59128639a19b4c2dd2f9b02201c8c4167d581864bedd4d1deb5596472e6e3ce29fe9e7996907a7b59c905d5490121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff06dbf9971c8dfd4a0c8c49f4f15c51de59ba13b2efa702682e26869843af9a87000000006a473044022012b47c12c7f6ad7d8b778fc4b5dcfd56a39325daf302f56e7b84753ba5216cfa022076bf571cf9e20facf70d2f134e8ed2de67aa08581a27ff3128bf93a9b594ac770121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff02fed727150000000017a914d5268b31131a652f9b6ddf57db62f02285cdfad1874e1d7835000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac37cf345d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563744071778},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563744071781},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563744118073},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"TakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563744118580}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); TestCoin::can_refund_htlc - .mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(CanRefundHtlc::CanRefundNow)))); + .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ok(CanRefundHtlc::CanRefundNow)))); static mut MY_PAYMENT_SENT_CALLED: bool = false; TestCoin::check_if_my_payment_sent.mock_safe(|_, _| { unsafe { MY_PAYMENT_SENT_CALLED = true }; - MockResult::Return(Box::new(futures01::future::ok(Some(eth_tx_for_test().into())))) + MockResult::Return(Box::pin(futures::future::ok(Some(eth_tx_for_test().into())))) }); static mut TX_SPEND_CALLED: bool = false; @@ -2794,7 +2841,7 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_errored_but_sent_and_spent_by_maker() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_wait":1563746537,"my_persistent_pub":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","started_at":1563743937,"taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"taker_payment_lock":1563751737,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743937741},{"event":{"data":{"maker_payment_locktime":1563759539,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"432c8272ac59b47dea2d299b5cf1ee64ea1917b9"},"type":"Negotiated"},"timestamp":1563744003530},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeSent"},"timestamp":1563744020598},{"event":{"data":{"tx_hash":"0cf4acbcefde53645851c5c6053ea61fe0cbb5f828a906d69eb809e0b071a03b","tx_hex":"0400008085202f89025d5ae3e8c87418c9b735f8f2f7d29e26820c33c9f30d53f2d31f8b99ea9b1490010000006a47304402201185c06ca575261c539b287175751b7de642eb7466c59128639a19b4c2dd2f9b02201c8c4167d581864bedd4d1deb5596472e6e3ce29fe9e7996907a7b59c905d5490121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff06dbf9971c8dfd4a0c8c49f4f15c51de59ba13b2efa702682e26869843af9a87000000006a473044022012b47c12c7f6ad7d8b778fc4b5dcfd56a39325daf302f56e7b84753ba5216cfa022076bf571cf9e20facf70d2f134e8ed2de67aa08581a27ff3128bf93a9b594ac770121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff02fed727150000000017a914d5268b31131a652f9b6ddf57db62f02285cdfad1874e1d7835000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac37cf345d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563744071778},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563744071781},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563744118073},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"TakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563744118580}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_wait":1563746537,"my_persistent_pub":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","started_at":1563743937,"taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"taker_payment_lock":1563751737,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743937741},{"event":{"data":{"maker_payment_locktime":1563759539,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"432c8272ac59b47dea2d299b5cf1ee64ea1917b9"},"type":"Negotiated"},"timestamp":1563744003530},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeSent"},"timestamp":1563744020598},{"event":{"data":{"tx_hash":"0cf4acbcefde53645851c5c6053ea61fe0cbb5f828a906d69eb809e0b071a03b","tx_hex":"0400008085202f89025d5ae3e8c87418c9b735f8f2f7d29e26820c33c9f30d53f2d31f8b99ea9b1490010000006a47304402201185c06ca575261c539b287175751b7de642eb7466c59128639a19b4c2dd2f9b02201c8c4167d581864bedd4d1deb5596472e6e3ce29fe9e7996907a7b59c905d5490121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff06dbf9971c8dfd4a0c8c49f4f15c51de59ba13b2efa702682e26869843af9a87000000006a473044022012b47c12c7f6ad7d8b778fc4b5dcfd56a39325daf302f56e7b84753ba5216cfa022076bf571cf9e20facf70d2f134e8ed2de67aa08581a27ff3128bf93a9b594ac770121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff02fed727150000000017a914d5268b31131a652f9b6ddf57db62f02285cdfad1874e1d7835000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac37cf345d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563744071778},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563744071781},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563744118073},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"TakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563744118580}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -2804,7 +2851,7 @@ mod taker_swap_tests { static mut MY_PAYMENT_SENT_CALLED: bool = false; TestCoin::check_if_my_payment_sent.mock_safe(|_, _| { unsafe { MY_PAYMENT_SENT_CALLED = true }; - MockResult::Return(Box::new(futures01::future::ok(Some(eth_tx_for_test().into())))) + MockResult::Return(Box::pin(futures::future::ok(Some(eth_tx_for_test().into())))) }); static mut SEARCH_TX_SPEND_CALLED: bool = false; @@ -2847,13 +2894,13 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_refund_failed_not_spent() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); TestCoin::can_refund_htlc - .mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(CanRefundHtlc::CanRefundNow)))); + .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ok(CanRefundHtlc::CanRefundNow)))); static mut SEARCH_TX_SPEND_CALLED: bool = false; TestCoin::search_for_swap_tx_spend_my.mock_safe(|_, _| { @@ -2890,13 +2937,13 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_refund_failed_not_spent_too_early_to_refund() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); TestCoin::can_refund_htlc - .mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(CanRefundHtlc::HaveToWait(1000))))); + .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ok(CanRefundHtlc::HaveToWait(1000))))); static mut SEARCH_TX_SPEND_CALLED: bool = false; TestCoin::search_for_swap_tx_spend_my.mock_safe(|_, _| { @@ -2921,7 +2968,7 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_refund_failed_spent_by_maker() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -2968,7 +3015,7 @@ mod taker_swap_tests { let ctx = mm_ctx_with_iguana(PASSPHRASE); // the json doesn't have Finished event at the end - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1564050693585},{"event":{"data":{"tx_hash":"539cb6dbdc25465bbccc575554f05d1bb04c70efce4316e41194e747375c3659","tx_hex":"0100000001ffc8a8a1b43b4dceed0f8b7dcc2f72fdda92d52f32d25cc21c6d2d498b82debd010000006a47304402203967b7f9f5532fa47116585c7d1bcba51861ea2059cca00409f34660db18e33a0220640991911852533a12fdfeb039fb9c8ca2c45482c6993bd84636af3670d49c1501210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff0200f2052a0100000017a914f2fa08ae416b576779ae5da975e5442663215fce87415173f9000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac0585395d"},"type":"TakerPaymentSent"},"timestamp":1564050695611},{"event":{"data":{"secret":"1b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093","transaction":{"tx_hash":"cc5af1cf68d246419fee49c3d74c0cd173599d115b86efe274368a614951bc47","tx_hex":"010000000159365c3747e79411e41643ceef704cb01b5df0545557ccbc5b4625dcdbb69c5300000000d747304402200e78e27d2f1c18676f98ca3dfa4e4a9eeaa8209b55f57b4dd5d9e1abdf034cfa0220623b5c22b62234cec230342aa306c497e43494b44ec2425b84e236b1bf01257001201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b6304a7a2395db175210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a88821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68ffffffff01008d380c010000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8c77395d"}},"type":"TakerPaymentSpent"},"timestamp":1564051092890},{"event":{"data":{"error":"lp_swap:1981] utxo:891] rpc_clients:738] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"67\", method: \"blockchain.transaction.broadcast\", params: [String(\"0400008085202f890182b342c114f806c5325f23f7e78dae5d186221ab502c86302c2c8082fa110f0a00000000d7473044022035791ea5548f87484065c9e1f0bdca9ebc699f2c7f51182c84f360102e32dc3d02200612ed53bca52d9c2568437f087598531534badf26229fe0f652ea72ddf03ca501201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b630420c1395db17521031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a888210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac68ffffffff01460ec000000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac967e395d000000000000000000000000000000\")] }, error: Transport(\"rpc_clients:668] All electrums are currently disconnected\") }"},"type":"MakerPaymentSpendFailed"},"timestamp":1564051092897}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1564050693585},{"event":{"data":{"tx_hash":"539cb6dbdc25465bbccc575554f05d1bb04c70efce4316e41194e747375c3659","tx_hex":"0100000001ffc8a8a1b43b4dceed0f8b7dcc2f72fdda92d52f32d25cc21c6d2d498b82debd010000006a47304402203967b7f9f5532fa47116585c7d1bcba51861ea2059cca00409f34660db18e33a0220640991911852533a12fdfeb039fb9c8ca2c45482c6993bd84636af3670d49c1501210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff0200f2052a0100000017a914f2fa08ae416b576779ae5da975e5442663215fce87415173f9000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac0585395d"},"type":"TakerPaymentSent"},"timestamp":1564050695611},{"event":{"data":{"secret":"1b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093","transaction":{"tx_hash":"cc5af1cf68d246419fee49c3d74c0cd173599d115b86efe274368a614951bc47","tx_hex":"010000000159365c3747e79411e41643ceef704cb01b5df0545557ccbc5b4625dcdbb69c5300000000d747304402200e78e27d2f1c18676f98ca3dfa4e4a9eeaa8209b55f57b4dd5d9e1abdf034cfa0220623b5c22b62234cec230342aa306c497e43494b44ec2425b84e236b1bf01257001201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b6304a7a2395db175210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a88821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68ffffffff01008d380c010000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8c77395d"}},"type":"TakerPaymentSpent"},"timestamp":1564051092890},{"event":{"data":{"error":"lp_swap:1981] utxo:891] rpc_clients:738] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"67\", method: \"blockchain.transaction.broadcast\", params: [String(\"0400008085202f890182b342c114f806c5325f23f7e78dae5d186221ab502c86302c2c8082fa110f0a00000000d7473044022035791ea5548f87484065c9e1f0bdca9ebc699f2c7f51182c84f360102e32dc3d02200612ed53bca52d9c2568437f087598531534badf26229fe0f652ea72ddf03ca501201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b630420c1395db17521031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a888210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac68ffffffff01460ec000000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac967e395d000000000000000000000000000000\")] }, error: Transport(\"rpc_clients:668] All electrums are currently disconnected\") }"},"type":"MakerPaymentSpendFailed"},"timestamp":1564051092897}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -3005,7 +3052,7 @@ mod taker_swap_tests { let ctx = mm_ctx_with_iguana(PASSPHRASE); // swap file contains neither maker_coin_swap_contract_address nor taker_coin_swap_contract_address - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -3040,7 +3087,7 @@ mod taker_swap_tests { let ctx = mm_ctx_with_iguana(PASSPHRASE); // swap file contains only maker_coin_swap_contract_address - let taker_saved_json = r#"{"type":"Taker","uuid":"49c79ea4-e1eb-4fb2-a0ef-265bded0b77f","events":[{"timestamp":1608542326909,"event":{"type":"Started","data":{"taker_coin":"RICK","maker_coin":"ETH","maker":"c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","my_persistent_pub":"02031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3","lock_duration":7800,"maker_amount":"0.1","taker_amount":"0.1","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":0,"taker_payment_requires_nota":false,"taker_payment_lock":1608550126,"uuid":"49c79ea4-e1eb-4fb2-a0ef-265bded0b77f","started_at":1608542326,"maker_payment_wait":1608545446,"maker_coin_start_block":14360,"taker_coin_start_block":723123,"maker_coin_swap_contract_address":"eA6D65434A15377081495a9E7C5893543E7c32cB"}}},{"timestamp":1608542327416,"event":{"type":"Negotiated","data":{"maker_payment_locktime":1608557926,"maker_pubkey":"03c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","secret_hash":"8b0221f3b977c1c65dddf17c1c28e2bbced9e7b4"}}},{"timestamp":1608542332604,"event":{"type":"TakerFeeSent","data":{"tx_hex":"0400008085202f89011ca964f77200b73d64b481f47de84098041d3470d6256e44f2741f080e2b11cf020000006b4830450221008a064f5e51ef8281d43eb7bcd016fed7e560ea1eb7b0713ec977602c96d8f79b02205bfaa6655b849b9922c03276b938273f2edb8fb9ffcaa2a9212d7220560f6060012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0246320000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac62752e27000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac7768e05f000000000000000000000000000000","tx_hash":"3793df28ed2aac6188d2c48ec65eff12eea301089d60da655fc96f598326d708"}}},{"timestamp":1608542334018,"event":{"type":"MakerPaymentReceived","data":{"tx_hex":"f8ef82021c80830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd88016345785d8a0000b884152cf3af50aebafeaf827c62c2eed09e265fa5aa9e013c0f27f0a88259f1aaa1279f0c32000000000000000000000000bab36286672fbdc7b250804bf6d14be0df69fa298b0221f3b977c1c65dddf17c1c28e2bbced9e7b4000000000000000000000000000000000000000000000000000000000000000000000000000000005fe0a5661ba0f18a0c5c349462b51dacd1a0761e4997d4572a01e48480c4e310d69a40308ad3a04510513f01a79c59f22c9cb79952547c8dfc4c74785b630f512d64369323e0c1","tx_hash":"6782323490584a2bc768cd5199506bfa1ed91e7515b35bb72fa269604b7dc0aa"}}},{"timestamp":1608542334019,"event":{"type":"MakerPaymentWaitConfirmStarted"}},{"timestamp":1608542334825,"event":{"type":"MakerPaymentValidatedAndConfirmed"}},{"timestamp":1608542337671,"event":{"type":"TakerPaymentSent","data":{"tx_hex":"0400008085202f890108d72683596fc95f65da609d0801a3ee12ff5ec68ec4d28861ac2aed28df9337010000006b48304502210086a03db599438b243bee2b02af56e23447f85d09854416b51305536b9ca5890e02204b288acdea4cdc7ab1ffbd9766a7bdf95f5bd02d2917dfb7089dbf29032591b0012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff03809698000000000017a914888e9e1816214c3960eac7b55e35521ca4426b0c870000000000000000166a148b0221f3b977c1c65dddf17c1c28e2bbced9e7b4fada9526000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac7f68e05f000000000000000000000000000000","tx_hash":"44fa493757df5fdca823bbac05a8b8feb5862d799d4947fd544abcd129feceea"}}},{"timestamp":1608542348271,"event":{"type":"TakerPaymentSpent","data":{"transaction":{"tx_hex":"0400008085202f8901eacefe29d1bc4a54fd47499d792d86b5feb8a805acbb23a8dc5fdf573749fa4400000000d74730440220508c853cc4f1fcb9e6aa00e704eef99adaee9a4ea63a1fd6393bb7ff18da02c802200396bb5d52157bd77ff26ac521ed75aca388d3ec1e5e3ebb7b3aed73c3d33ec50120df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e004c6b6304ee86e05fb1752102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ac6782012088a9148b0221f3b977c1c65dddf17c1c28e2bbced9e7b4882103c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3edac68ffffffff0198929800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac725ae05f000000000000000000000000000000","tx_hash":"9376dde62249802a0aba8259f51def9bb2e509af85a5ec7df04b479a9da28a29"},"secret":"df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e"}}},{"timestamp":1608542349372,"event":{"type":"MakerPaymentSpent","data":{"tx_hex":"f90107821fb980830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd80b8a402ed292b50aebafeaf827c62c2eed09e265fa5aa9e013c0f27f0a88259f1aaa1279f0c32000000000000000000000000000000000000000000000000016345785d8a0000df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b2d0d6c2c785217457b69b922a2a9cea98f71e91ca0ed6a4942a78c7ae6eb3c9dec496459a9ef68b34cb389acd939d13d3ecaf7e4aca021bb77e80fc60acf25a7a01cc1272b1b76594a521fb1abe1322d650e58a672c2","tx_hash":"c2d206e665aee159a5ab9aff60f76444e97bdad8f9152eccb6ca07d9204974ca"}}},{"timestamp":1608542349373,"event":{"type":"Finished"}}],"maker_amount":"0.1","maker_coin":"ETH","taker_amount":"0.1","taker_coin":"RICK","gui":"nogui","mm_version":"1a6082121","success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"]}"#; + let taker_saved_json = r#"{"type":"Taker","uuid":"49c79ea4-e1eb-4fb2-a0ef-265bded0b77f","events":[{"timestamp":1608542326909,"event":{"type":"Started","data":{"taker_coin":"RICK","maker_coin":"ETH","maker":"c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","my_persistent_pub":"02031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3","lock_duration":7800,"maker_amount":"0.1","taker_amount":"0.1","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":0,"taker_payment_requires_nota":false,"taker_payment_lock":1608550126,"uuid":"49c79ea4-e1eb-4fb2-a0ef-265bded0b77f","started_at":1608542326,"maker_payment_wait":1608545446,"maker_coin_start_block":14360,"taker_coin_start_block":723123,"maker_coin_swap_contract_address":"eA6D65434A15377081495a9E7C5893543E7c32cB"}}},{"timestamp":1608542327416,"event":{"type":"Negotiated","data":{"maker_payment_locktime":1608557926,"maker_pubkey":"03c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","secret_hash":"8b0221f3b977c1c65dddf17c1c28e2bbced9e7b4"}}},{"timestamp":1608542332604,"event":{"type":"TakerFeeSent","data":{"tx_hex":"0400008085202f89011ca964f77200b73d64b481f47de84098041d3470d6256e44f2741f080e2b11cf020000006b4830450221008a064f5e51ef8281d43eb7bcd016fed7e560ea1eb7b0713ec977602c96d8f79b02205bfaa6655b849b9922c03276b938273f2edb8fb9ffcaa2a9212d7220560f6060012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0246320000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac62752e27000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac7768e05f000000000000000000000000000000","tx_hash":"3793df28ed2aac6188d2c48ec65eff12eea301089d60da655fc96f598326d708"}}},{"timestamp":1608542334018,"event":{"type":"MakerPaymentReceived","data":{"tx_hex":"f8ef82021c80830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd88016345785d8a0000b884152cf3af50aebafeaf827c62c2eed09e265fa5aa9e013c0f27f0a88259f1aaa1279f0c32000000000000000000000000bab36286672fbdc7b250804bf6d14be0df69fa298b0221f3b977c1c65dddf17c1c28e2bbced9e7b4000000000000000000000000000000000000000000000000000000000000000000000000000000005fe0a5661ba0f18a0c5c349462b51dacd1a0761e4997d4572a01e48480c4e310d69a40308ad3a04510513f01a79c59f22c9cb79952547c8dfc4c74785b630f512d64369323e0c1","tx_hash":"6782323490584a2bc768cd5199506bfa1ed91e7515b35bb72fa269604b7dc0aa"}}},{"timestamp":1608542334019,"event":{"type":"MakerPaymentWaitConfirmStarted"}},{"timestamp":1608542334825,"event":{"type":"MakerPaymentValidatedAndConfirmed"}},{"timestamp":1608542337671,"event":{"type":"TakerPaymentSent","data":{"tx_hex":"0400008085202f890108d72683596fc95f65da609d0801a3ee12ff5ec68ec4d28861ac2aed28df9337010000006b48304502210086a03db599438b243bee2b02af56e23447f85d09854416b51305536b9ca5890e02204b288acdea4cdc7ab1ffbd9766a7bdf95f5bd02d2917dfb7089dbf29032591b0012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff03809698000000000017a914888e9e1816214c3960eac7b55e35521ca4426b0c870000000000000000166a148b0221f3b977c1c65dddf17c1c28e2bbced9e7b4fada9526000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac7f68e05f000000000000000000000000000000","tx_hash":"44fa493757df5fdca823bbac05a8b8feb5862d799d4947fd544abcd129feceea"}}},{"timestamp":1608542348271,"event":{"type":"TakerPaymentSpent","data":{"transaction":{"tx_hex":"0400008085202f8901eacefe29d1bc4a54fd47499d792d86b5feb8a805acbb23a8dc5fdf573749fa4400000000d74730440220508c853cc4f1fcb9e6aa00e704eef99adaee9a4ea63a1fd6393bb7ff18da02c802200396bb5d52157bd77ff26ac521ed75aca388d3ec1e5e3ebb7b3aed73c3d33ec50120df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e004c6b6304ee86e05fb1752102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ac6782012088a9148b0221f3b977c1c65dddf17c1c28e2bbced9e7b4882103c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3edac68ffffffff0198929800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac725ae05f000000000000000000000000000000","tx_hash":"9376dde62249802a0aba8259f51def9bb2e509af85a5ec7df04b479a9da28a29"},"secret":"df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e"}}},{"timestamp":1608542349372,"event":{"type":"MakerPaymentSpent","data":{"tx_hex":"f90107821fb980830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd80b8a402ed292b50aebafeaf827c62c2eed09e265fa5aa9e013c0f27f0a88259f1aaa1279f0c32000000000000000000000000000000000000000000000000016345785d8a0000df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b2d0d6c2c785217457b69b922a2a9cea98f71e91ca0ed6a4942a78c7ae6eb3c9dec496459a9ef68b34cb389acd939d13d3ecaf7e4aca021bb77e80fc60acf25a7a01cc1272b1b76594a521fb1abe1322d650e58a672c2","tx_hash":"c2d206e665aee159a5ab9aff60f76444e97bdad8f9152eccb6ca07d9204974ca"}}},{"timestamp":1608542349373,"event":{"type":"Finished"}}],"maker_amount":"0.1","maker_coin":"ETH","taker_amount":"0.1","taker_coin":"RICK","gui":"nogui","mm_version":"1a6082121","success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"]}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -3074,7 +3121,7 @@ mod taker_swap_tests { fn test_recoverable() { // Swap ended with MakerPaymentWaitConfirmFailed event. // MM2 did not attempt to send the payment in this case so swap is not recoverable. - let swap: TakerSavedSwap = json::from_str(r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"data":{"error":"error"},"type":"MakerPaymentWaitConfirmFailed"},"timestamp":1564051092897},{"event":{"type":"Finished"},"timestamp":1564051092900}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#).unwrap(); + let swap: TakerSavedSwap = json::from_str(r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"data":{"error":"error"},"type":"MakerPaymentWaitConfirmFailed"},"timestamp":1564051092897},{"event":{"type":"Finished"},"timestamp":1564051092900}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#).unwrap(); assert!(!swap.is_recoverable()); } @@ -3153,7 +3200,7 @@ mod taker_swap_tests { #[test] fn locked_amount_should_not_use_paid_from_trading_vol_fee() { - use crate::mm2::lp_swap::get_locked_amount; + use crate::lp_swap::get_locked_amount; let taker_saved_json = r#"{ "type": "Taker", diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index 256503350b..29f3d07277 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -1,16 +1,16 @@ use super::swap_v2_common::*; use super::{LockedAmount, LockedAmountInfo, SavedTradeFee, SwapsContext, TakerSwapPreparedParams, NEGOTIATE_SEND_INTERVAL, NEGOTIATION_TIMEOUT_SEC}; -use crate::mm2::lp_swap::swap_lock::SwapLock; -use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_swap, recv_swap_v2_msg, swap_v2_topic, - SecretHashAlgo, SwapConfirmationsSettings, TransactionIdentifier, MAX_STARTED_AT_DIFF, - TAKER_SWAP_V2_TYPE}; -use crate::mm2::lp_swap::{swap_v2_pb::*, NO_REFUND_FEE}; +use crate::lp_swap::swap_lock::SwapLock; +use crate::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_swap, recv_swap_v2_msg, swap_v2_topic, + SecretHashAlgo, SwapConfirmationsSettings, TransactionIdentifier, MAX_STARTED_AT_DIFF, + TAKER_SWAP_V2_TYPE}; +use crate::lp_swap::{swap_v2_pb::*, NO_REFUND_FEE}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use coins::{CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, ParseCoinAssocTypes, RefundFundingSecretArgs, - RefundPaymentArgs, SendTakerFundingArgs, SpendMakerPaymentArgs, SwapTxTypeWithSecretHash, + RefundTakerPaymentArgs, SendTakerFundingArgs, SpendMakerPaymentArgs, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, ToBytes, TradeFee, TradePreimageValue, Transaction, TxPreimageWithSig, ValidateMakerPaymentArgs}; use common::executor::abortable_queue::AbortableQueue; @@ -33,14 +33,14 @@ use std::marker::PhantomData; use uuid::Uuid; cfg_native!( - use crate::mm2::database::my_swaps::{insert_new_swap_v2, SELECT_MY_SWAP_V2_BY_UUID}; + use crate::database::my_swaps::{insert_new_swap_v2, SELECT_MY_SWAP_V2_BY_UUID}; use common::async_blocking; use db_common::sqlite::rusqlite::{named_params, Error as SqlError, Result as SqlResult, Row}; use db_common::sqlite::rusqlite::types::Type as SqlType; ); cfg_wasm32!( - use crate::mm2::lp_swap::swap_wasm_db::{MySwapsFiltersTable, SavedSwapTable}; + use crate::lp_swap::swap_wasm_db::{MySwapsFiltersTable, SavedSwapTable}; ); // This is needed to have Debug on messages @@ -389,11 +389,13 @@ pub struct TakerSwapStateMachine, state_machine: &mut Self::StateMachine) -> StateResult { let args = SendTakerFundingArgs { - time_lock: state_machine.taker_funding_locktime(), + funding_time_lock: state_machine.taker_funding_locktime(), + payment_time_lock: state_machine.taker_payment_locktime(), taker_secret_hash: &state_machine.taker_secret_hash(), + maker_secret_hash: &self.negotiation_data.maker_secret_hash, maker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker.to_bytes(), dex_fee: &state_machine.dex_fee, premium_amount: state_machine.taker_premium.to_decimal(), @@ -1569,7 +1573,7 @@ impl break, @@ -1889,18 +1896,21 @@ impl tx, diff --git a/mm2src/mm2_main/src/lp_swap/trade_preimage.rs b/mm2src/mm2_main/src/lp_swap/trade_preimage.rs index 7f64b052db..cb31ed63e9 100644 --- a/mm2src/mm2_main/src/lp_swap/trade_preimage.rs +++ b/mm2src/mm2_main/src/lp_swap/trade_preimage.rs @@ -1,6 +1,6 @@ use super::check_balance::CheckBalanceError; use super::{maker_swap_trade_preimage, taker_swap_trade_preimage, MakerTradePreimage, TakerTradePreimage}; -use crate::mm2::lp_ordermatch::{MakerOrderBuildError, TakerOrderBuildError}; +use crate::lp_ordermatch::{MakerOrderBuildError, TakerOrderBuildError}; use coins::{is_wallet_only_ticker, lp_coinfind_or_err, BalanceError, CoinFindError, TradeFee, TradePreimageError}; use common::HttpStatusCode; use crypto::CryptoCtxError; diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index e84ea8e98c..20a154b28c 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -2,31 +2,28 @@ use common::HttpStatusCode; use crypto::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, CryptoCtx, CryptoInitError, EncryptedData, MnemonicError}; use http::StatusCode; +use itertools::Itertools; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use serde::de::DeserializeOwned; -use serde_json::{self as json}; +use serde_json::{self as json, Value as Json}; cfg_wasm32! { - use crate::mm2::lp_wallet::mnemonics_wasm_db::{WalletsDb, WalletsDBError}; + use crate::lp_wallet::mnemonics_wasm_db::{WalletsDb, WalletsDBError}; use mm2_core::mm_ctx::from_ctx; use mm2_db::indexed_db::{ConstructibleDb, DbLocked, InitDbResult}; - use mnemonics_wasm_db::{read_encrypted_passphrase_if_available, save_encrypted_passphrase}; + use mnemonics_wasm_db::{read_all_wallet_names, read_encrypted_passphrase_if_available, save_encrypted_passphrase}; use std::sync::Arc; type WalletsDbLocked<'a> = DbLocked<'a, WalletsDb>; } cfg_native! { - use mnemonics_storage::{read_encrypted_passphrase_if_available, save_encrypted_passphrase, WalletsStorageError}; + use mnemonics_storage::{read_all_wallet_names, read_encrypted_passphrase_if_available, save_encrypted_passphrase, WalletsStorageError}; } -#[cfg(not(target_arch = "wasm32"))] -#[path = "lp_wallet/mnemonics_storage.rs"] -mod mnemonics_storage; -#[cfg(target_arch = "wasm32")] -#[path = "lp_wallet/mnemonics_wasm_db.rs"] -mod mnemonics_wasm_db; +#[cfg(not(target_arch = "wasm32"))] mod mnemonics_storage; +#[cfg(target_arch = "wasm32")] mod mnemonics_wasm_db; type WalletInitResult = Result>; @@ -308,8 +305,8 @@ fn initialize_crypto_context(ctx: &MmArc, passphrase: &str) -> WalletInitResult< pub(crate) async fn initialize_wallet_passphrase(ctx: &MmArc) -> WalletInitResult<()> { let (wallet_name, passphrase) = deserialize_wallet_config(ctx)?; ctx.wallet_name - .pin(wallet_name.clone()) - .map_to_mm(WalletInitError::InternalError)?; + .set(wallet_name.clone()) + .map_to_mm(|_| WalletInitError::InternalError("Already Initialized".to_string()))?; let passphrase = process_passphrase_logic(ctx, wallet_name, passphrase).await?; if let Some(passphrase) = passphrase { @@ -503,3 +500,53 @@ pub async fn get_mnemonic_rpc(ctx: MmArc, req: GetMnemonicRequest) -> MmResult, + activated_wallet: Option, +} + +#[derive(Debug, Display, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum GetWalletsError { + #[display(fmt = "Wallets storage error: {}", _0)] + WalletsStorageError(String), + #[display(fmt = "Internal error: {}", _0)] + Internal(String), +} + +impl HttpStatusCode for GetWalletsError { + fn status_code(&self) -> StatusCode { + match self { + GetWalletsError::WalletsStorageError(_) | GetWalletsError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl From for GetWalletsError { + fn from(e: WalletsStorageError) -> Self { GetWalletsError::WalletsStorageError(e.to_string()) } +} + +#[cfg(target_arch = "wasm32")] +impl From for GetWalletsError { + fn from(e: WalletsDBError) -> Self { GetWalletsError::WalletsStorageError(e.to_string()) } +} + +/// Retrieves all created wallets and the currently activated wallet. +pub async fn get_wallet_names_rpc(ctx: MmArc, _req: Json) -> MmResult { + // We want to return wallet names in the same order for both native and wasm32 targets. + let wallets = read_all_wallet_names(&ctx).await?.sorted().collect(); + // Note: `ok_or` is used here on `Constructible>` to handle the case where the wallet name is not set. + // `wallet_name` can be `None` in the case of no-login mode. + let activated_wallet = ctx.wallet_name.get().ok_or(GetWalletsError::Internal( + "`wallet_name` not initialized yet!".to_string(), + ))?; + + Ok(GetWalletNamesResponse { + wallet_names: wallets, + activated_wallet: activated_wallet.clone(), + }) +} diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs index 3cf40e61fb..e779f7b86a 100644 --- a/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs @@ -1,7 +1,7 @@ use crypto::EncryptedData; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; -use mm2_io::fs::ensure_file_is_writable; +use mm2_io::fs::{ensure_file_is_writable, list_files_by_extension}; type WalletsStorageResult = Result>; @@ -47,6 +47,7 @@ pub(super) async fn save_encrypted_passphrase( pub(super) async fn read_encrypted_passphrase_if_available(ctx: &MmArc) -> WalletsStorageResult> { let wallet_name = ctx .wallet_name + .get() .ok_or(WalletsStorageError::Internal( "`wallet_name` not initialized yet!".to_string(), ))? @@ -61,3 +62,10 @@ pub(super) async fn read_encrypted_passphrase_if_available(ctx: &MmArc) -> Walle )) }) } + +pub(super) async fn read_all_wallet_names(ctx: &MmArc) -> WalletsStorageResult> { + let wallet_names = list_files_by_extension(&ctx.db_root(), "dat", false) + .await + .mm_err(|e| WalletsStorageError::FsReadError(format!("Error reading wallets directory: {}", e)))?; + Ok(wallet_names) +} diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs index f7bf2674e4..fa66cada1c 100644 --- a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs @@ -1,4 +1,4 @@ -use crate::mm2::lp_wallet::WalletsContext; +use crate::lp_wallet::WalletsContext; use async_trait::async_trait; use crypto::EncryptedData; use mm2_core::mm_ctx::MmArc; @@ -126,6 +126,7 @@ pub(super) async fn read_encrypted_passphrase_if_available(ctx: &MmArc) -> Walle let wallet_name = ctx .wallet_name + .get() .ok_or(WalletsDBError::Internal( "`wallet_name` not initialized yet!".to_string(), ))? @@ -144,3 +145,16 @@ pub(super) async fn read_encrypted_passphrase_if_available(ctx: &MmArc) -> Walle }) .transpose() } + +pub(super) async fn read_all_wallet_names(ctx: &MmArc) -> WalletsDBResult> { + let wallets_ctx = WalletsContext::from_ctx(ctx).map_to_mm(WalletsDBError::Internal)?; + + let db = wallets_ctx.wallets_db().await?; + let transaction = db.transaction().await?; + let table = transaction.table::().await?; + + let all_items = table.get_all_items().await?; + let wallet_names = all_items.into_iter().map(|(_, item)| item.wallet_name); + + Ok(wallet_names) +} diff --git a/mm2src/mm2_main/src/mm2.rs b/mm2src/mm2_main/src/mm2.rs index 5bd85358cb..4876069e6d 100644 --- a/mm2src/mm2_main/src/mm2.rs +++ b/mm2src/mm2_main/src/mm2.rs @@ -21,12 +21,28 @@ // Copyright © 2023 Pampex LTD and TillyHK LTD. All rights reserved. // +#![feature(hash_raw_entry)] +// `mockable` implementation uses these +#![allow( + forgetting_references, + forgetting_copy_types, + clippy::swap_ptr_to_ref, + clippy::forget_non_drop, + clippy::let_unit_value +)] #![cfg_attr(target_arch = "wasm32", allow(dead_code))] #![cfg_attr(target_arch = "wasm32", allow(unused_imports))] +#[macro_use] extern crate common; +#[macro_use] extern crate gstuff; +#[macro_use] extern crate serde_json; +#[macro_use] extern crate serde_derive; +#[macro_use] extern crate ser_error_derive; +#[cfg(test)] extern crate mm2_test_helpers; + #[cfg(not(target_arch = "wasm32"))] use common::block_on; use common::crash_reports::init_crash_reports; -use common::double_panic_crash; +use common::log; use common::log::LogLevel; use common::password_policy::password_policy; use mm2_core::mm_ctx::MmCtxBuilder; @@ -38,7 +54,6 @@ use lp_swap::PAYMENT_LOCKTIME; use std::sync::atomic::Ordering; use gstuff::slurp; - use serde::ser::Serialize; use serde_json::{self as json, Value as Json}; @@ -48,25 +63,25 @@ use std::process::exit; use std::ptr::null; use std::str; -#[path = "lp_native_dex.rs"] mod lp_native_dex; pub use self::lp_native_dex::init_hw; pub use self::lp_native_dex::lp_init; use coins::update_coins_config; use mm2_err_handle::prelude::*; -#[cfg(not(target_arch = "wasm32"))] -#[path = "database.rs"] -pub mod database; - -#[path = "heartbeat_event.rs"] pub mod heartbeat_event; -#[path = "lp_dispatcher.rs"] pub mod lp_dispatcher; -#[path = "lp_message_service.rs"] pub mod lp_message_service; -#[path = "lp_network.rs"] pub mod lp_network; -#[path = "lp_ordermatch.rs"] pub mod lp_ordermatch; -#[path = "lp_stats.rs"] pub mod lp_stats; -#[path = "lp_swap.rs"] pub mod lp_swap; -#[path = "lp_wallet.rs"] pub mod lp_wallet; -#[path = "rpc.rs"] pub mod rpc; +#[cfg(not(target_arch = "wasm32"))] pub mod database; + +pub mod heartbeat_event; +pub mod lp_dispatcher; +pub mod lp_healthcheck; +pub mod lp_message_service; +mod lp_native_dex; +pub mod lp_network; +pub mod lp_ordermatch; +pub mod lp_stats; +pub mod lp_swap; +pub mod lp_wallet; +pub mod rpc; +#[cfg(all(target_arch = "wasm32", test))] mod wasm_tests; pub const PASSWORD_MAXIMUM_CONSECUTIVE_CHARACTERS: usize = 3; @@ -112,7 +127,7 @@ pub async fn lp_main( ) -> Result<(), String> { let log_filter = params.filter.unwrap_or_default(); // Logger can be initialized once. - // If `mm2` is linked as a library, and `mm2` is restarted, `init_logger` returns an error. + // If `kdf` is linked as a library, and `kdf` is restarted, `init_logger` returns an error. init_logger(log_filter, params.conf["silent_console"].as_bool().unwrap_or_default()).ok(); let conf = params.conf; @@ -145,10 +160,33 @@ pub async fn lp_main( .with_datetime(datetime.clone()) .into_mm_arc(); ctx_cb(try_s!(ctx.ffi_handle())); + + #[cfg(not(target_arch = "wasm32"))] + spawn_ctrl_c_handler(ctx.clone()); + try_s!(lp_init(ctx, version, datetime).await); Ok(()) } +/// Handles CTRL-C signals and shutdowns the KDF runtime gracefully. +/// +/// It's important to spawn this task as soon as `Ctx` is in the correct state. +#[cfg(not(target_arch = "wasm32"))] +fn spawn_ctrl_c_handler(ctx: mm2_core::mm_ctx::MmArc) { + use crate::lp_dispatcher::{dispatch_lp_event, StopCtxEvent}; + + common::executor::spawn(async move { + tokio::signal::ctrl_c() + .await + .expect("Couldn't listen for the CTRL-C signal."); + + log::info!("Wrapping things up and shutting down..."); + + dispatch_lp_event(ctx.clone(), StopCtxEvent.into()).await; + ctx.stop().await.expect("Couldn't stop the KDF runtime."); + }); +} + fn help() { const HELP_MSG: &str = r#"Command-line options. The first command-line argument is special and designates the mode. @@ -166,16 +204,14 @@ Some (but not all) of the JSON configuration parameters (* - required): {"coins": [{"name": "dash", "coin": "DASH", ...}, ...], ...}. coins .. Information about the currencies: their ticker symbols, names, ports, addresses, etc. If the field isn't present on the command line then we try loading it from the 'coins' file. - crash .. Simulate a crash to check how the crash handling works. dbdir .. MM database path. 'DB' by default. - gui .. The information about GUI app using MM2 instance. Included in swap statuses shared with network. + gui .. The information about GUI app using KDF instance. Included in swap statuses shared with network. .. It's recommended to put essential info to this field (application name, OS, version, etc). .. e.g. AtomicDEX iOS 1.0.1000. myipaddr .. IP address to bind to for P2P networking. netid .. Subnetwork. Affects ports and keys. passphrase * .. Wallet seed. Compressed WIFs and hexadecimal ECDSA keys (prefixed with 0x) are also accepted. - panic .. Simulate a panic to see if backtrace works. rpccors .. Access-Control-Allow-Origin header value to be used in all the RPC responses. Default is currently 'http://localhost:3000' rpcip .. IP address to bind to for RPC server. Overrides the 127.0.0.1 default @@ -184,11 +220,10 @@ Some (but not all) of the JSON configuration parameters (* - required): rpc_local_only .. MM forbids some RPC requests from not loopback (localhost) IPs as additional security measure. Defaults to `true`, set `false` to disable. `Use with caution`. rpcport .. If > 1000 overrides the 7783 default. - i_am_seed .. Activate the seed node mode (acting as a relay for mm2 clients). + i_am_seed .. Activate the seed node mode (acting as a relay for kdf clients). Defaults to `false`. seednodes .. Seednode IPs that node will use. At least one seed IP must be present if the node is not a seed itself. - stderr .. Print a message to stderr and exit. wif .. `1` to add WIFs to the information we provide about a coin. Environment variables: @@ -233,16 +268,6 @@ pub fn mm2_main(version: String, datetime: String) { // we're not checking them for the mode switches in order not to risk [untrusted] data being mistaken for a mode switch. let first_arg = args_os.get(1).and_then(|arg| arg.to_str()); - if first_arg == Some("panic") { - panic!("panic message") - } - if first_arg == Some("crash") { - double_panic_crash() - } - if first_arg == Some("stderr") { - eprintln!("This goes to stderr"); - return; - } if first_arg == Some("update_config") { match on_update_config(&args_os) { Ok(_) => println!("Success"), @@ -252,7 +277,7 @@ pub fn mm2_main(version: String, datetime: String) { } if first_arg == Some("--version") || first_arg == Some("-v") || first_arg == Some("version") { - println!("AtomicDEX API: {version}"); + println!("Komodo DeFi Framework: {version}"); return; } @@ -266,7 +291,7 @@ pub fn mm2_main(version: String, datetime: String) { return; } - log!("AtomicDEX API {} DT {}", version, datetime); + log!("Komodo DeFi Framework {} DT {}", version, datetime); if let Err(err) = run_lp_main(first_arg, &|_| (), version, datetime) { log!("{}", err); @@ -278,22 +303,23 @@ pub fn mm2_main(version: String, datetime: String) { /// Parses and returns the `first_arg` as JSON. /// Attempts to load the config from `MM2.json` file if `first_arg` is None pub fn get_mm2config(first_arg: Option<&str>) -> Result { - let conf_path = common::kdf_config_file(); - let conf_from_file = slurp(&conf_path); let conf = match first_arg { - Some(s) => s, + Some(s) => s.to_owned(), None => { + let conf_path = common::kdf_config_file().map_err(|e| e.to_string())?; + let conf_from_file = slurp(&conf_path); + if conf_from_file.is_empty() { return ERR!( "Config is not set from command line arg and {} file doesn't exist.", conf_path.display() ); } - try_s!(std::str::from_utf8(&conf_from_file)) + try_s!(String::from_utf8(conf_from_file)) }, }; - let mut conf: Json = match json::from_str(conf) { + let mut conf: Json = match json::from_str(&conf) { Ok(json) => json, // Syntax or io errors may include the conf string in the error message so we don't want to take risks and show these errors internals in the log. // If new variants are added to the Error enum, there can be a risk of exposing the conf string in the error message when updating serde_json so @@ -302,7 +328,7 @@ pub fn get_mm2config(first_arg: Option<&str>) -> Result { }; if conf["coins"].is_null() { - let coins_path = common::kdf_coins_file(); + let coins_path = common::kdf_coins_file().map_err(|e| e.to_string())?; let coins_from_file = slurp(&coins_path); if coins_from_file.is_empty() { diff --git a/mm2src/mm2_main/src/notification/telegram/telegram.rs b/mm2src/mm2_main/src/notification/telegram/telegram.rs index edfc5c15f7..3879920767 100644 --- a/mm2src/mm2_main/src/notification/telegram/telegram.rs +++ b/mm2src/mm2_main/src/notification/telegram/telegram.rs @@ -1,4 +1,4 @@ -use crate::mm2::lp_message_service::{MessageResult, MessageServiceTraits}; +use crate::lp_message_service::{MessageResult, MessageServiceTraits}; use async_trait::async_trait; use derive_more::Display; use mm2_net::transport::{post_json, SlurpError}; @@ -58,8 +58,8 @@ impl MessageServiceTraits for TgClient { #[cfg(all(test, not(target_arch = "wasm32")))] mod telegram_tests { - use crate::mm2::lp_message_service::telegram::{ChatIdRegistry, TgClient}; - use crate::mm2::lp_message_service::MessageServiceTraits; + use crate::lp_message_service::telegram::{ChatIdRegistry, TgClient}; + use crate::lp_message_service::MessageServiceTraits; use common::block_on; use std::env::var; diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index b0657b0d00..3bf81d6370 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -1,14 +1,17 @@ use super::*; -use crate::mm2::lp_ordermatch::new_protocol::{MakerOrderUpdated, PubkeyKeepAlive}; +use crate::lp_ordermatch::new_protocol::{MakerOrderUpdated, PubkeyKeepAlive}; use coins::{MmCoin, TestCoin}; use common::{block_on, executor::spawn}; use crypto::privkey::key_pair_from_seed; use db_common::sqlite::rusqlite::Connection; use futures::{channel::mpsc, StreamExt}; use mm2_core::mm_ctx::{MmArc, MmCtx}; +use mm2_libp2p::application::request_response::ordermatch::OrdermatchRequest; +use mm2_libp2p::application::request_response::P2PRequest; +use mm2_libp2p::behaviours::atomicdex::generate_ed25519_keypair; +use mm2_libp2p::p2p_ctx::P2PContext; use mm2_libp2p::AdexBehaviourCmd; use mm2_libp2p::{decode_message, PeerId}; -use mm2_net::p2p::P2PContext; use mm2_test_helpers::for_tests::mm_ctx_with_iguana; use mocktopus::mocking::*; use rand::{seq::SliceRandom, thread_rng, Rng}; @@ -938,7 +941,14 @@ fn test_taker_order_cancellable() { fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver { let (tx, rx) = mpsc::channel(10); - let p2p_ctx = P2PContext::new(tx); + + let p2p_key = { + let crypto_ctx = CryptoCtx::from_ctx(ctx).unwrap(); + let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); + key.take() + }; + + let p2p_ctx = P2PContext::new(tx, generate_ed25519_keypair(p2p_key)); p2p_ctx.store_to_mm_arc(ctx); let ordermatch_ctx = OrdermatchContext::from_ctx(ctx).unwrap(); @@ -1045,7 +1055,10 @@ fn test_cancel_by_single_coin() { let rx = prepare_for_cancel_by(&ctx); let connection = Connection::open_in_memory().unwrap(); - let _ = ctx.sqlite_connection.pin(Arc::new(Mutex::new(connection))); + let _ = ctx + .sqlite_connection + .set(Arc::new(Mutex::new(connection))) + .map_err(|_| "Already Initialized".to_string()); delete_my_maker_order.mock_safe(|_, _, _| MockResult::Return(Box::new(futures01::future::ok(())))); delete_my_taker_order.mock_safe(|_, _, _| MockResult::Return(Box::new(futures01::future::ok(())))); @@ -1064,7 +1077,10 @@ fn test_cancel_by_pair() { let rx = prepare_for_cancel_by(&ctx); let connection = Connection::open_in_memory().unwrap(); - let _ = ctx.sqlite_connection.pin(Arc::new(Mutex::new(connection))); + let _ = ctx + .sqlite_connection + .set(Arc::new(Mutex::new(connection))) + .map_err(|_| "Already Initialized".to_string()); delete_my_maker_order.mock_safe(|_, _, _| MockResult::Return(Box::new(futures01::future::ok(())))); delete_my_taker_order.mock_safe(|_, _, _| MockResult::Return(Box::new(futures01::future::ok(())))); @@ -1087,7 +1103,10 @@ fn test_cancel_by_all() { let rx = prepare_for_cancel_by(&ctx); let connection = Connection::open_in_memory().unwrap(); - let _ = ctx.sqlite_connection.pin(Arc::new(Mutex::new(connection))); + let _ = ctx + .sqlite_connection + .set(Arc::new(Mutex::new(connection))) + .map_err(|_| "Already Initialized".to_string()); delete_my_maker_order.mock_safe(|_, _, _| MockResult::Return(Box::new(futures01::future::ok(())))); delete_my_taker_order.mock_safe(|_, _, _| MockResult::Return(Box::new(futures01::future::ok(())))); @@ -1671,7 +1690,14 @@ fn pubkey_and_secret_for_test(passphrase: &str) -> (String, [u8; 32]) { fn init_p2p_context(ctx: &MmArc) -> (mpsc::Sender, mpsc::Receiver) { let (cmd_tx, cmd_rx) = mpsc::channel(10); - let p2p_context = P2PContext::new(cmd_tx.clone()); + + let p2p_key = { + let crypto_ctx = CryptoCtx::from_ctx(ctx).unwrap(); + let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); + key.take() + }; + + let p2p_context = P2PContext::new(cmd_tx.clone(), generate_ed25519_keypair(p2p_key)); p2p_context.store_to_mm_arc(ctx); (cmd_tx, cmd_rx) } diff --git a/mm2src/mm2_main/src/rpc.rs b/mm2src/mm2_main/src/rpc.rs index 92422738f9..4e4947e151 100644 --- a/mm2src/mm2_main/src/rpc.rs +++ b/mm2src/mm2_main/src/rpc.rs @@ -20,36 +20,32 @@ // Copyright © 2023 Pampex LTD and TillyHK LTD. All rights reserved. // -use crate::mm2::rpc::rate_limiter::RateLimitError; +use crate::rpc::rate_limiter::RateLimitError; use common::log::{error, info}; -use common::{err_to_rpc_json_string, err_tp_rpc_json, HttpStatusCode, APPLICATION_JSON}; +use common::{err_to_rpc_json_string, err_tp_rpc_json, HttpStatusCode}; use derive_more::Display; use futures::future::{join_all, FutureExt}; -use http::header::{HeaderValue, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE}; +use http::header::{HeaderValue, ACCESS_CONTROL_ALLOW_ORIGIN}; use http::request::Parts; use http::{Method, Request, Response, StatusCode}; -use lazy_static::lazy_static; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_rpc::mm_protocol::{MmRpcBuilder, MmRpcResponse, MmRpcVersion}; -use regex::Regex; use serde::Serialize; use serde_json::{self as json, Value as Json}; -use std::borrow::Cow; use std::net::SocketAddr; cfg_native! { use hyper::{self, Body, Server}; + use futures::channel::oneshot; use mm2_net::sse_handler::{handle_sse, SSE_ENDPOINT}; } #[path = "rpc/dispatcher/dispatcher.rs"] mod dispatcher; #[path = "rpc/dispatcher/dispatcher_legacy.rs"] mod dispatcher_legacy; -#[path = "rpc/lp_commands/lp_commands.rs"] pub mod lp_commands; -#[path = "rpc/lp_commands/lp_commands_legacy.rs"] -pub mod lp_commands_legacy; -#[path = "rpc/rate_limiter.rs"] mod rate_limiter; +pub mod lp_commands; +mod rate_limiter; /// Lists the RPC method not requiring the "userpass" authentication. /// None is also public to skip auth and display proper error in case of method is missing @@ -178,35 +174,6 @@ fn response_from_dispatcher_error( response.serialize_http_response() } -pub fn escape_answer<'a, S: Into>>(input: S) -> Cow<'a, str> { - lazy_static! { - static ref REGEX: Regex = Regex::new("[<>&]").unwrap(); - } - - let input = input.into(); - let mut last_match = 0; - - if REGEX.is_match(&input) { - let matches = REGEX.find_iter(&input); - let mut output = String::with_capacity(input.len()); - for mat in matches { - let (begin, end) = (mat.start(), mat.end()); - output.push_str(&input[last_match..begin]); - match &input[begin..end] { - "<" => output.push_str("<"), - ">" => output.push_str(">"), - "&" => output.push_str("&"), - _ => unreachable!(), - } - last_match = end; - } - output.push_str(&input[last_match..]); - Cow::Owned(output) - } else { - input - } -} - async fn process_single_request(ctx: MmArc, req: Json, client: SocketAddr) -> Result>, String> { let local_only = ctx.conf["rpc_local_only"].as_bool().unwrap_or(true); if req["mmrpc"].is_null() { @@ -269,40 +236,39 @@ async fn rpc_service(req: Request, ctx_h: u32, client: SocketAddr) -> Resp // https://github.com/artemii235/SuperNET/issues/219 let rpc_cors = match ctx.conf["rpccors"].as_str() { Some(s) => try_sf!(HeaderValue::from_str(s)), - None => HeaderValue::from_static("http://localhost:3000"), + None => { + if ctx.is_https() { + HeaderValue::from_static("https://localhost:3000") + } else { + HeaderValue::from_static("http://localhost:3000") + } + }, }; // Convert the native Hyper stream into a portable stream of `Bytes`. let (req, req_body) = req.into_parts(); - let req_bytes = try_sf!(hyper::body::to_bytes(req_body).await, ACCESS_CONTROL_ALLOW_ORIGIN => rpc_cors); - let req_str = String::from_utf8_lossy(req_bytes.as_ref()); - let is_invalid_input = req_str.chars().any(|c| c == '<' || c == '>' || c == '&'); - if is_invalid_input { + + if req.method == Method::OPTIONS { return Response::builder() - .status(500) - .header(CONTENT_TYPE, APPLICATION_JSON) - .body(Body::from(err_to_rpc_json_string("Invalid input"))) + .status(StatusCode::OK) + .header(ACCESS_CONTROL_ALLOW_ORIGIN, rpc_cors) + .header("Access-Control-Allow-Methods", "POST, OPTIONS") + .header("Access-Control-Allow-Headers", "Content-Type") + .header("Access-Control-Max-Age", "3600") + .body(Body::empty()) .unwrap(); } - let req_json: Json = try_sf!(json::from_slice(&req_bytes), ACCESS_CONTROL_ALLOW_ORIGIN => rpc_cors); + + let req_json = { + let req_bytes = try_sf!(hyper::body::to_bytes(req_body).await, ACCESS_CONTROL_ALLOW_ORIGIN => rpc_cors); + try_sf!(json::from_slice(&req_bytes), ACCESS_CONTROL_ALLOW_ORIGIN => rpc_cors) + }; let res = try_sf!(process_rpc_request(ctx, req, req_json, client).await, ACCESS_CONTROL_ALLOW_ORIGIN => rpc_cors); let (mut parts, body) = res.into_parts(); parts.headers.insert(ACCESS_CONTROL_ALLOW_ORIGIN, rpc_cors); - let body_escaped = match std::str::from_utf8(&body) { - Ok(body_utf8) => { - let escaped = escape_answer(body_utf8); - escaped.as_bytes().to_vec() - }, - Err(_) => { - return Response::builder() - .status(500) - .header(CONTENT_TYPE, APPLICATION_JSON) - .body(Body::from(err_to_rpc_json_string("Non UTF-8 output"))) - .unwrap(); - }, - }; - Response::from_parts(parts, Body::from(body_escaped)) + + Response::from_parts(parts, Body::from(body)) } // TODO: This should exclude TCP internals, as including them results in having to @@ -351,6 +317,34 @@ pub extern "C" fn spawn_rpc(ctx_h: u32) { Ok((cert_chain, privkey)) } + // Handles incoming HTTP requests. + async fn handle_request( + req: Request, + remote_addr: SocketAddr, + ctx_h: u32, + is_event_stream_enabled: bool, + ) -> Result, Infallible> { + let (tx, rx) = oneshot::channel(); + // We execute the request in a separate task to avoid it being left uncompleted if the client disconnects. + // So what's inside the spawn here will complete till completion (or panic). + common::executor::spawn(async move { + if is_event_stream_enabled && req.uri().path() == SSE_ENDPOINT { + tx.send(handle_sse(req, ctx_h).await).ok(); + } else { + tx.send(rpc_service(req, ctx_h, remote_addr).await).ok(); + } + }); + // On the other hand, this `.await` might be aborted if the client disconnects. + match rx.await { + Ok(res) => Ok(res), + Err(_) => { + let err = "The RPC service aborted without responding."; + error!("{}", err); + Ok(Response::builder().status(500).body(Body::from(err)).unwrap()) + }, + } + } + // NB: We need to manually handle the incoming connections in order to get the remote IP address, // cf. https://github.com/hyperium/hyper/issues/1410#issuecomment-419510220. // Although if the ability to access the remote IP address is solved by the Hyper in the future @@ -358,28 +352,19 @@ pub extern "C" fn spawn_rpc(ctx_h: u32) { // cf. https://github.com/hyperium/hyper/pull/1640. let ctx = MmArc::from_ffi_handle(ctx_h).expect("No context"); - let is_event_stream_enabled = ctx.event_stream_configuration.is_some(); - let make_svc_fut = move |remote_addr: SocketAddr| async move { - Ok::<_, Infallible>(service_fn(move |req: Request| async move { - if is_event_stream_enabled && req.uri().path() == SSE_ENDPOINT { - let res = handle_sse(req, ctx_h).await?; - return Ok::<_, Infallible>(res); - } - - let res = rpc_service(req, ctx_h, remote_addr).await; - Ok::<_, Infallible>(res) - })) - }; - //The `make_svc` macro creates a `make_service_fn` for a specified socket type. // `$socket_type`: The socket type with a `remote_addr` method that returns a `SocketAddr`. macro_rules! make_svc { ($socket_type:ty) => { make_service_fn(move |socket: &$socket_type| { let remote_addr = socket.remote_addr(); - make_svc_fut(remote_addr) + async move { + Ok::<_, Infallible>(service_fn(move |req: Request| { + handle_request(req, remote_addr, ctx_h, is_event_stream_enabled) + })) + } }) }; } @@ -425,7 +410,7 @@ pub extern "C" fn spawn_rpc(ctx_h: u32) { $port, now_sec() ); - let _ = $ctx.rpc_started.pin(true); + let _ = $ctx.rpc_started.set(true); server }); } @@ -494,7 +479,7 @@ pub fn spawn_rpc(ctx_h: u32) { use std::sync::Mutex; let ctx = MmArc::from_ffi_handle(ctx_h).expect("No context"); - if ctx.wasm_rpc.is_some() { + if ctx.wasm_rpc.get().is_some() { error!("RPC is initialized already"); return; } @@ -527,12 +512,12 @@ pub fn spawn_rpc(ctx_h: u32) { ctx.spawner().spawn(fut); // even if the [`MmCtx::wasm_rpc`] is initialized already, the spawned future above will be shutdown - if let Err(e) = ctx.wasm_rpc.pin(request_tx) { - error!("'MmCtx::wasm_rpc' is initialized already: {}", e); + if ctx.wasm_rpc.set(request_tx).is_err() { + error!("'MmCtx::wasm_rpc' is initialized already"); return; }; - if let Err(e) = ctx.rpc_started.pin(true) { - error!("'MmCtx::rpc_started' is set already: {}", e); + if ctx.rpc_started.set(true).is_err() { + error!("'MmCtx::rpc_started' is set already"); return; } diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 271fbf48cc..937db9631b 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -1,16 +1,26 @@ use super::{DispatcherError, DispatcherResult, PUBLIC_METHODS}; -use crate::mm2::lp_native_dex::init_hw::{cancel_init_trezor, init_trezor, init_trezor_status, init_trezor_user_action}; +use crate::lp_healthcheck::peer_connection_healthcheck_rpc; +use crate::lp_native_dex::init_hw::{cancel_init_trezor, init_trezor, init_trezor_status, init_trezor_user_action}; #[cfg(target_arch = "wasm32")] -use crate::mm2::lp_native_dex::init_metamask::{cancel_connect_metamask, connect_metamask, connect_metamask_status}; -use crate::mm2::lp_ordermatch::{best_orders_rpc_v2, orderbook_rpc_v2, start_simple_market_maker_bot, - stop_simple_market_maker_bot}; -use crate::mm2::lp_swap::swap_v2_rpcs::{active_swaps_rpc, my_recent_swaps_rpc, my_swap_status_rpc}; -use crate::mm2::lp_wallet::get_mnemonic_rpc; -use crate::mm2::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; -use crate::{mm2::lp_stats::{add_node_to_version_stat, remove_node_from_version_stat, start_version_stat_collection, - stop_version_stat_collection, update_version_stat_collection}, - mm2::lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}, - mm2::rpc::lp_commands::{get_public_key, get_public_key_hash, get_shared_db_id, trezor_connection_status}}; +use crate::lp_native_dex::init_metamask::{cancel_connect_metamask, connect_metamask, connect_metamask_status}; +use crate::lp_ordermatch::{best_orders_rpc_v2, orderbook_rpc_v2, start_simple_market_maker_bot, + stop_simple_market_maker_bot}; +use crate::lp_stats::{add_node_to_version_stat, remove_node_from_version_stat, start_version_stat_collection, + stop_version_stat_collection, update_version_stat_collection}; +use crate::lp_swap::swap_v2_rpcs::{active_swaps_rpc, my_recent_swaps_rpc, my_swap_status_rpc}; +use crate::lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}; +use crate::lp_wallet::{get_mnemonic_rpc, get_wallet_names_rpc}; +use crate::rpc::lp_commands::db_id::get_shared_db_id; +use crate::rpc::lp_commands::one_inch::rpcs::{one_inch_v6_0_classic_swap_contract_rpc, + one_inch_v6_0_classic_swap_create_rpc, + one_inch_v6_0_classic_swap_liquidity_sources_rpc, + one_inch_v6_0_classic_swap_quote_rpc, + one_inch_v6_0_classic_swap_tokens_rpc}; +use crate::rpc::lp_commands::pubkey::*; +use crate::rpc::lp_commands::tokens::get_token_info; +use crate::rpc::lp_commands::tokens::{approve_token_rpc, get_token_allowance_rpc}; +use crate::rpc::lp_commands::trezor::trezor_connection_status; +use crate::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; use coins::eth::EthCoin; use coins::my_tx_history_v2::my_tx_history_v2_rpc; use coins::rpc_command::tendermint::{ibc_chains, ibc_transfer_channels}; @@ -28,7 +38,7 @@ use coins::rpc_command::{account_balance::account_balance, init_scan_for_new_addresses::{cancel_scan_for_new_addresses, init_scan_for_new_addresses, init_scan_for_new_addresses_status}, init_withdraw::{cancel_withdraw, init_withdraw, withdraw_status, withdraw_user_action}}; -#[cfg(feature = "enable-sia")] use coins::sia::SiaCoin; +#[cfg(feature = "enable-sia")] use coins::siacoin::SiaCoin; use coins::tendermint::{TendermintCoin, TendermintToken}; use coins::utxo::bch::BchCoin; use coins::utxo::qtum::QtumCoin; @@ -38,13 +48,6 @@ use coins::z_coin::ZCoin; use coins::{add_delegation, get_my_address, get_raw_transaction, get_staking_infos, get_swap_transaction_fee_policy, nft, remove_delegation, set_swap_transaction_fee_policy, sign_message, sign_raw_transaction, verify_message, withdraw}; -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -use coins::{SolanaCoin, SplToken}; use coins_activation::{cancel_init_l2, cancel_init_platform_coin_with_tokens, cancel_init_standalone_coin, cancel_init_token, enable_platform_coin_with_tokens, enable_token, init_l2, init_l2_status, init_l2_user_action, init_platform_coin_with_tokens, init_platform_coin_with_tokens_status, @@ -165,6 +168,8 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, active_swaps_rpc).await, "add_delegation" => handle_mmrpc(ctx, request, add_delegation).await, "add_node_to_version_stat" => handle_mmrpc(ctx, request, add_node_to_version_stat).await, + "approve_token" => handle_mmrpc(ctx, request, approve_token_rpc).await, + "get_token_allowance" => handle_mmrpc(ctx, request, get_token_allowance_rpc).await, "best_orders" => handle_mmrpc(ctx, request, best_orders_rpc_v2).await, "clear_nft_db" => handle_mmrpc(ctx, request, clear_nft_db).await, "enable_bch_with_tokens" => handle_mmrpc(ctx, request, enable_platform_coin_with_tokens::).await, @@ -190,6 +195,8 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, get_raw_transaction).await, "get_shared_db_id" => handle_mmrpc(ctx, request, get_shared_db_id).await, "get_staking_infos" => handle_mmrpc(ctx, request, get_staking_infos).await, + "get_token_info" => handle_mmrpc(ctx, request, get_token_info).await, + "get_wallet_names" => handle_mmrpc(ctx, request, get_wallet_names_rpc).await, "max_maker_vol" => handle_mmrpc(ctx, request, max_maker_vol).await, "my_recent_swaps" => handle_mmrpc(ctx, request, my_recent_swaps_rpc).await, "my_swap_status" => handle_mmrpc(ctx, request, my_swap_status_rpc).await, @@ -213,6 +220,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, withdraw).await, "ibc_chains" => handle_mmrpc(ctx, request, ibc_chains).await, "ibc_transfer_channels" => handle_mmrpc(ctx, request, ibc_transfer_channels).await, + "peer_connection_healthcheck" => handle_mmrpc(ctx, request, peer_connection_healthcheck_rpc).await, "withdraw_nft" => handle_mmrpc(ctx, request, withdraw_nft).await, "start_eth_fee_estimator" => handle_mmrpc(ctx, request, start_eth_fee_estimator).await, "stop_eth_fee_estimator" => handle_mmrpc(ctx, request, stop_eth_fee_estimator).await, @@ -221,17 +229,13 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, set_swap_transaction_fee_policy).await, "send_asked_data" => handle_mmrpc(ctx, request, send_asked_data_rpc).await, "z_coin_tx_history" => handle_mmrpc(ctx, request, coins::my_tx_history_v2::z_coin_tx_history_rpc).await, - #[cfg(not(target_arch = "wasm32"))] - native_only_methods => match native_only_methods { - #[cfg(all(feature = "enable-solana", not(target_os = "ios"), not(target_os = "android")))] - "enable_solana_with_tokens" => { - handle_mmrpc(ctx, request, enable_platform_coin_with_tokens::).await - }, - #[cfg(all(feature = "enable-solana", not(target_os = "ios"), not(target_os = "android")))] - "enable_spl" => handle_mmrpc(ctx, request, enable_token::).await, - _ => MmError::err(DispatcherError::NoSuchMethod), + "1inch_v6_0_classic_swap_contract" => handle_mmrpc(ctx, request, one_inch_v6_0_classic_swap_contract_rpc).await, + "1inch_v6_0_classic_swap_quote" => handle_mmrpc(ctx, request, one_inch_v6_0_classic_swap_quote_rpc).await, + "1inch_v6_0_classic_swap_create" => handle_mmrpc(ctx, request, one_inch_v6_0_classic_swap_create_rpc).await, + "1inch_v6_0_classic_swap_liquidity_sources" => { + handle_mmrpc(ctx, request, one_inch_v6_0_classic_swap_liquidity_sources_rpc).await }, - #[cfg(target_arch = "wasm32")] + "1inch_v6_0_classic_swap_tokens" => handle_mmrpc(ctx, request, one_inch_v6_0_classic_swap_tokens_rpc).await, _ => MmError::err(DispatcherError::NoSuchMethod), } } diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs index 795b76cee0..5f4b14f8b4 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs @@ -7,14 +7,14 @@ use mm2_core::mm_ctx::MmArc; use serde_json::{self as json, Value as Json}; use std::net::SocketAddr; -use super::lp_commands_legacy::*; -use crate::mm2::lp_ordermatch::{best_orders_rpc, buy, cancel_all_orders_rpc, cancel_order_rpc, my_orders, - order_status, orderbook_depth_rpc, orderbook_rpc, orders_history_by_filter, sell, - set_price, update_maker_order_rpc}; -use crate::mm2::lp_swap::{active_swaps_rpc, all_swaps_uuids_by_filter, ban_pubkey_rpc, coins_needed_for_kick_start, - import_swaps, list_banned_pubkeys_rpc, max_taker_vol, my_recent_swaps_rpc, my_swap_status, - recover_funds_of_swap, stats_swap_status, unban_pubkeys_rpc}; -use crate::mm2::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; +use super::lp_commands::legacy::*; +use crate::lp_ordermatch::{best_orders_rpc, buy, cancel_all_orders_rpc, cancel_order_rpc, my_orders, order_status, + orderbook_depth_rpc, orderbook_rpc, orders_history_by_filter, sell, set_price, + update_maker_order_rpc}; +use crate::lp_swap::{active_swaps_rpc, all_swaps_uuids_by_filter, ban_pubkey_rpc, coins_needed_for_kick_start, + import_swaps, list_banned_pubkeys_rpc, max_taker_vol, my_recent_swaps_rpc, my_swap_status, + recover_funds_of_swap, stats_swap_status, unban_pubkeys_rpc}; +use crate::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; use coins::{convert_address, convert_utxo_address, get_enabled_coins, get_trade_fee, kmd_rewards_info, my_tx_history, send_raw_transaction, set_required_confirmations, set_requires_notarization, show_priv_key, validate_address}; @@ -73,11 +73,11 @@ pub fn dispatcher(req: Json, ctx: MmArc) -> DispatcherRes { "electrum" => hyres(electrum(ctx, req)), "enable" => hyres(enable(ctx, req)), "get_enabled_coins" => hyres(get_enabled_coins(ctx)), + "get_directly_connected_peers" => hyres(get_directly_connected_peers(ctx)), "get_gossip_mesh" => hyres(get_gossip_mesh(ctx)), "get_gossip_peer_topics" => hyres(get_gossip_peer_topics(ctx)), "get_gossip_topic_peers" => hyres(get_gossip_topic_peers(ctx)), "get_my_peer_id" => hyres(get_my_peer_id(ctx)), - "get_peers_info" => hyres(get_peers_info(ctx)), "get_relay_mesh" => hyres(get_relay_mesh(ctx)), "get_trade_fee" => hyres(get_trade_fee(ctx, req)), // "fundvalue" => lp_fundvalue (ctx, req, false), @@ -98,7 +98,6 @@ pub fn dispatcher(req: Json, ctx: MmArc) -> DispatcherRes { "order_status" => hyres(order_status(ctx, req)), "orderbook" => hyres(orderbook_rpc(ctx, req)), "orderbook_depth" => hyres(orderbook_depth_rpc(ctx, req)), - "sim_panic" => hyres(sim_panic(req)), "recover_funds_of_swap" => hyres(recover_funds_of_swap(ctx, req)), "sell" => hyres(sell(ctx, req)), "show_priv_key" => hyres(show_priv_key(ctx, req)), @@ -144,7 +143,7 @@ pub async fn process_single_request( /// The set of functions that convert the result of the updated handlers into the legacy format. mod into_legacy { use super::*; - use crate::mm2::lp_swap; + use crate::lp_swap; pub async fn withdraw(ctx: MmArc, req: Json) -> Result>, String> { let params = try_s!(json::from_value(req)); diff --git a/mm2src/mm2_main/src/rpc/lp_commands/db_id.rs b/mm2src/mm2_main/src/rpc/lp_commands/db_id.rs new file mode 100644 index 0000000000..29fa399bd0 --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/db_id.rs @@ -0,0 +1,18 @@ +use crate::rpc::lp_commands::pubkey::GetPublicKeyError; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::mm_error::MmError; +use rpc::v1::types::H160 as H160Json; +use serde_json::Value as Json; + +pub type GetSharedDbIdResult = Result>; +pub type GetSharedDbIdError = GetPublicKeyError; + +#[derive(Serialize)] +pub struct GetSharedDbIdResponse { + shared_db_id: H160Json, +} + +pub async fn get_shared_db_id(ctx: MmArc, _req: Json) -> GetSharedDbIdResult { + let shared_db_id = ctx.shared_db_id().to_owned().into(); + Ok(GetSharedDbIdResponse { shared_db_id }) +} diff --git a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs b/mm2src/mm2_main/src/rpc/lp_commands/legacy.rs similarity index 87% rename from mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs rename to mm2src/mm2_main/src/rpc/lp_commands/legacy.rs index 7e61b8c642..828e145f23 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/legacy.rs @@ -20,25 +20,24 @@ // use coins::{lp_coinfind, lp_coinfind_any, lp_coininit, CoinsContext, MmCoinEnum}; +use common::custom_futures::timeout::FutureTimerExt; use common::executor::Timer; -use common::log::error; use common::{rpc_err_response, rpc_response, HyRes}; use futures::compat::Future01CompatExt; use http::Response; use mm2_core::mm_ctx::MmArc; +use mm2_libp2p::p2p_ctx::P2PContext; use mm2_metrics::MetricsOps; -use mm2_net::p2p::P2PContext; use mm2_number::construct_detailed; use mm2_rpc::data::legacy::{BalanceResponse, CoinInitResponse, Mm2RpcResult, MmVersionResponse, Status}; use serde_json::{self as json, Value as Json}; -use std::borrow::Cow; use std::collections::HashSet; use uuid::Uuid; -use crate::mm2::lp_dispatcher::{dispatch_lp_event, StopCtxEvent}; -use crate::mm2::lp_network::subscribe_to_topic; -use crate::mm2::lp_ordermatch::{cancel_orders_by, get_matching_orders, CancelBy}; -use crate::mm2::lp_swap::{active_swaps_using_coins, tx_helper_topic, watcher_topic}; +use crate::lp_dispatcher::{dispatch_lp_event, StopCtxEvent}; +use crate::lp_network::subscribe_to_topic; +use crate::lp_ordermatch::{cancel_orders_by, get_matching_orders, CancelBy}; +use crate::lp_swap::{active_swaps_using_coins, tx_helper_topic, watcher_topic}; const INTERNAL_SERVER_ERROR_CODE: u16 = 500; const RESPONSE_OK_STATUS_CODE: u16 = 200; @@ -139,7 +138,16 @@ pub async fn disable_coin(ctx: MmArc, req: Json) -> Result>, St pub async fn electrum(ctx: MmArc, req: Json) -> Result>, String> { let ticker = try_s!(req["coin"].as_str().ok_or("No 'coin' field")).to_owned(); let coin: MmCoinEnum = try_s!(lp_coininit(&ctx, &ticker, &req).await); - let balance = try_s!(coin.my_balance().compat().await); + let balance = match coin.my_balance().compat().timeout_secs(5.).await { + Ok(Ok(balance)) => balance, + // If the coin was activated successfully but the balance query failed (most probably due to faulty + // electrum servers), remove the coin as the whole request is a failure now from the POV of the GUI. + err => { + let coins_ctx = try_s!(CoinsContext::from_ctx(&ctx)); + coins_ctx.remove_coin(coin).await; + return Err(ERRL!("Deactivated coin due to error in balance querying: {:?}", err)); + }, + }; let res = CoinInitResponse { result: "success".into(), address: try_s!(coin.my_address()), @@ -242,29 +250,13 @@ pub async fn my_balance(ctx: MmArc, req: Json) -> Result>, Stri Ok(try_s!(Response::builder().body(res))) } -#[cfg(not(target_arch = "wasm32"))] -async fn close_async_connection(ctx: &MmArc) { - if let Some(async_conn) = ctx.async_sqlite_connection.as_option() { - let mut conn = async_conn.lock().await; - if let Err(e) = conn.close().await { - error!("Error stopping AsyncConnection: {}", e); - } - } -} - pub async fn stop(ctx: MmArc) -> Result>, String> { dispatch_lp_event(ctx.clone(), StopCtxEvent.into()).await; // Should delay the shutdown a bit in order not to trip the "stop" RPC call in unit tests. // Stopping immediately leads to the "stop" RPC call failing with the "errno 10054" sometimes. let fut = async move { Timer::sleep(0.05).await; - - #[cfg(not(target_arch = "wasm32"))] - close_async_connection(&ctx).await; - - if let Err(e) = ctx.stop() { - error!("Error stopping MmCtx: {}", e); - } + ctx.stop().await.expect("Couldn't stop the KDF runtime."); }; // Please note we shouldn't use `MmCtx::spawner` to spawn this future, @@ -276,36 +268,6 @@ pub async fn stop(ctx: MmArc) -> Result>, String> { Ok(try_s!(Response::builder().body(res))) } -pub async fn sim_panic(req: Json) -> Result>, String> { - #[derive(Deserialize)] - struct Req { - #[serde(default)] - mode: String, - } - let req: Req = try_s!(json::from_value(req)); - - #[derive(Serialize)] - struct Ret<'a> { - /// Supported panic modes. - #[serde(skip_serializing_if = "Vec::is_empty")] - modes: Vec>, - } - let ret: Ret; - - if req.mode.is_empty() { - ret = Ret { - modes: vec!["simple".into()], - } - } else if req.mode == "simple" { - panic!("sim_panic: simple") - } else { - return ERR!("No such mode: {}", req.mode); - } - - let js = try_s!(json::to_vec(&ret)); - Ok(try_s!(Response::builder().body(js))) -} - pub fn version(ctx: MmArc) -> HyRes { match json::to_vec(&MmVersionResponse { result: ctx.mm_version.clone(), @@ -316,10 +278,10 @@ pub fn version(ctx: MmArc) -> HyRes { } } -pub async fn get_peers_info(ctx: MmArc) -> Result>, String> { +pub async fn get_directly_connected_peers(ctx: MmArc) -> Result>, String> { let ctx = P2PContext::fetch_from_mm_arc(&ctx); let cmd_tx = ctx.cmd_tx.lock().clone(); - let result = mm2_libp2p::get_peers_info(cmd_tx).await; + let result = mm2_libp2p::get_directly_connected_peers(cmd_tx).await; let result = json!({ "result": result, }); @@ -372,7 +334,9 @@ pub async fn get_relay_mesh(ctx: MmArc) -> Result>, String> { } pub async fn get_my_peer_id(ctx: MmArc) -> Result>, String> { - let peer_id = try_s!(ctx.peer_id.ok_or("Peer ID is not initialized")); + let p2p_ctx = P2PContext::fetch_from_mm_arc(&ctx); + let peer_id = p2p_ctx.peer_id().to_string(); + let result = json!({ "result": peer_id, }); diff --git a/mm2src/mm2_main/src/rpc/lp_commands/mod.rs b/mm2src/mm2_main/src/rpc/lp_commands/mod.rs new file mode 100644 index 0000000000..e61d5aead8 --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/mod.rs @@ -0,0 +1,6 @@ +pub(crate) mod db_id; +pub mod legacy; +pub(crate) mod one_inch; +pub(crate) mod pubkey; +pub(crate) mod tokens; +pub(crate) mod trezor; diff --git a/mm2src/mm2_main/src/rpc/lp_commands/one_inch.rs b/mm2src/mm2_main/src/rpc/lp_commands/one_inch.rs new file mode 100644 index 0000000000..3d47853294 --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/one_inch.rs @@ -0,0 +1,5 @@ +//! RPC implementation for integration with 1inch swap API provider. + +pub mod errors; +pub mod rpcs; +pub mod types; diff --git a/mm2src/mm2_main/src/rpc/lp_commands/one_inch/errors.rs b/mm2src/mm2_main/src/rpc/lp_commands/one_inch/errors.rs new file mode 100644 index 0000000000..8ee65af984 --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/one_inch/errors.rs @@ -0,0 +1,99 @@ +use coins::{eth::u256_to_big_decimal, NumConversError}; +use common::{HttpStatusCode, StatusCode}; +use enum_derives::EnumFromStringify; +use mm2_number::BigDecimal; +use ser_error_derive::SerializeErrorType; +use serde::Serialize; +use trading_api::one_inch_api::errors::ApiClientError; + +#[derive(Debug, Display, Serialize, SerializeErrorType, EnumFromStringify)] +#[serde(tag = "error_type", content = "error_data")] +pub enum ApiIntegrationRpcError { + #[from_stringify("coins::CoinFindError")] + NoSuchCoin(String), + #[display(fmt = "EVM token needed")] + CoinTypeError, + #[display(fmt = "NFT not supported")] + NftNotSupported, + #[display(fmt = "Chain not supported")] + ChainNotSupported, + #[display(fmt = "Must be same chain")] + DifferentChains, + #[from_stringify("coins::UnexpectedDerivationMethod")] + MyAddressError(String), + InvalidParam(String), + #[display(fmt = "Parameter {param} out of bounds, value: {value}, min: {min} max: {max}")] + OutOfBounds { + param: String, + value: String, + min: String, + max: String, + }, + #[display(fmt = "allowance not enough for 1inch contract, available: {allowance}, needed: {amount}")] + OneInchAllowanceNotEnough { + allowance: BigDecimal, + amount: BigDecimal, + }, + #[display(fmt = "1inch API error: {}", _0)] + OneInchError(ApiClientError), + ApiDataError(String), +} + +impl HttpStatusCode for ApiIntegrationRpcError { + fn status_code(&self) -> StatusCode { + match self { + ApiIntegrationRpcError::NoSuchCoin { .. } => StatusCode::NOT_FOUND, + ApiIntegrationRpcError::CoinTypeError + | ApiIntegrationRpcError::NftNotSupported + | ApiIntegrationRpcError::ChainNotSupported + | ApiIntegrationRpcError::DifferentChains + | ApiIntegrationRpcError::MyAddressError(_) + | ApiIntegrationRpcError::InvalidParam(_) + | ApiIntegrationRpcError::OutOfBounds { .. } + | ApiIntegrationRpcError::OneInchAllowanceNotEnough { .. } => StatusCode::BAD_REQUEST, + ApiIntegrationRpcError::OneInchError(_) | ApiIntegrationRpcError::ApiDataError(_) => { + StatusCode::BAD_GATEWAY + }, + } + } +} + +impl ApiIntegrationRpcError { + pub(crate) fn from_api_error(error: ApiClientError, decimals: Option) -> Self { + match error { + ApiClientError::InvalidParam(error) => ApiIntegrationRpcError::InvalidParam(error), + ApiClientError::OutOfBounds { param, value, min, max } => { + ApiIntegrationRpcError::OutOfBounds { param, value, min, max } + }, + ApiClientError::TransportError(_) + | ApiClientError::ParseBodyError { .. } + | ApiClientError::GeneralApiError { .. } => ApiIntegrationRpcError::OneInchError(error), + ApiClientError::AllowanceNotEnough { allowance, amount, .. } => { + ApiIntegrationRpcError::OneInchAllowanceNotEnough { + allowance: u256_to_big_decimal(allowance, decimals.unwrap_or_default()).unwrap_or_default(), + amount: u256_to_big_decimal(amount, decimals.unwrap_or_default()).unwrap_or_default(), + } + }, + } + } +} + +/// Error aggregator for errors of conversion of api returned values +#[derive(Debug, Display, Serialize)] +pub(crate) struct FromApiValueError(String); + +impl From for FromApiValueError { + fn from(err: NumConversError) -> Self { Self(err.to_string()) } +} + +impl From for FromApiValueError { + fn from(err: primitive_types::Error) -> Self { Self(format!("{:?}", err)) } +} + +impl From for FromApiValueError { + fn from(err: hex::FromHexError) -> Self { Self(err.to_string()) } +} + +impl From for FromApiValueError { + fn from(err: ethereum_types::FromDecStrErr) -> Self { Self(err.to_string()) } +} diff --git a/mm2src/mm2_main/src/rpc/lp_commands/one_inch/rpcs.rs b/mm2src/mm2_main/src/rpc/lp_commands/one_inch/rpcs.rs new file mode 100644 index 0000000000..a0c384463d --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/one_inch/rpcs.rs @@ -0,0 +1,439 @@ +use super::errors::ApiIntegrationRpcError; +use super::types::{AggregationContractRequest, ClassicSwapCreateRequest, ClassicSwapLiquiditySourcesRequest, + ClassicSwapLiquiditySourcesResponse, ClassicSwapQuoteRequest, ClassicSwapResponse, + ClassicSwapTokensRequest, ClassicSwapTokensResponse}; +use coins::eth::{display_eth_address, wei_from_big_decimal, EthCoin, EthCoinType}; +use coins::{lp_coinfind_or_err, CoinWithDerivationMethod, MmCoin, MmCoinEnum}; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; +use trading_api::one_inch_api::client::ApiClient; +use trading_api::one_inch_api::types::{ClassicSwapCreateParams, ClassicSwapQuoteParams, ProtocolsResponse, + TokensResponse}; + +/// "1inch_v6_0_classic_swap_contract" rpc impl +/// used to get contract address (for e.g. to approve funds) +pub async fn one_inch_v6_0_classic_swap_contract_rpc( + _ctx: MmArc, + _req: AggregationContractRequest, +) -> MmResult { + Ok(ApiClient::classic_swap_contract().to_owned()) +} + +/// "1inch_classic_swap_quote" rpc impl +pub async fn one_inch_v6_0_classic_swap_quote_rpc( + ctx: MmArc, + req: ClassicSwapQuoteRequest, +) -> MmResult { + let (base, base_contract) = get_coin_for_one_inch(&ctx, &req.base).await?; + let (rel, rel_contract) = get_coin_for_one_inch(&ctx, &req.rel).await?; + api_supports_pair(&base, &rel)?; + let sell_amount = wei_from_big_decimal(&req.amount.to_decimal(), base.decimals()) + .mm_err(|err| ApiIntegrationRpcError::InvalidParam(err.to_string()))?; + let query_params = ClassicSwapQuoteParams::new(base_contract, rel_contract, sell_amount.to_string()) + .with_fee(req.fee) + .with_protocols(req.protocols) + .with_gas_price(req.gas_price) + .with_complexity_level(req.complexity_level) + .with_parts(req.parts) + .with_main_route_parts(req.main_route_parts) + .with_gas_limit(req.gas_limit) + .with_include_tokens_info(Some(req.include_tokens_info)) + .with_include_protocols(Some(req.include_protocols)) + .with_include_gas(Some(req.include_gas)) + .with_connector_tokens(req.connector_tokens) + .build_query_params() + .mm_err(|api_err| ApiIntegrationRpcError::from_api_error(api_err, Some(base.decimals())))?; + let quote = ApiClient::new(ctx) + .mm_err(|api_err| ApiIntegrationRpcError::from_api_error(api_err, Some(base.decimals())))? + .call_swap_api( + base.chain_id(), + ApiClient::get_quote_method().to_owned(), + Some(query_params), + ) + .await + .mm_err(|api_err| ApiIntegrationRpcError::from_api_error(api_err, Some(base.decimals())))?; // use 'base' as amount in errors is in the src coin + ClassicSwapResponse::from_api_classic_swap_data(quote, rel.decimals()) // use 'rel' as quote value is in the dst coin + .mm_err(|err| ApiIntegrationRpcError::ApiDataError(err.to_string())) +} + +/// "1inch_classic_swap_create" rpc implementation +/// This rpc actually returns a transaction to call the 1inch swap aggregation contract. GUI should sign it and send to the chain. +/// We don't verify the transaction in any way and trust the 1inch api. +pub async fn one_inch_v6_0_classic_swap_create_rpc( + ctx: MmArc, + req: ClassicSwapCreateRequest, +) -> MmResult { + let (base, base_contract) = get_coin_for_one_inch(&ctx, &req.base).await?; + let (rel, rel_contract) = get_coin_for_one_inch(&ctx, &req.rel).await?; + api_supports_pair(&base, &rel)?; + let sell_amount = wei_from_big_decimal(&req.amount.to_decimal(), base.decimals()) + .mm_err(|err| ApiIntegrationRpcError::InvalidParam(err.to_string()))?; + let single_address = base.derivation_method().single_addr_or_err().await?; + + let query_params = ClassicSwapCreateParams::new( + base_contract, + rel_contract, + sell_amount.to_string(), + display_eth_address(&single_address), + req.slippage, + ) + .with_fee(req.fee) + .with_protocols(req.protocols) + .with_gas_price(req.gas_price) + .with_complexity_level(req.complexity_level) + .with_parts(req.parts) + .with_main_route_parts(req.main_route_parts) + .with_gas_limit(req.gas_limit) + .with_include_tokens_info(Some(req.include_tokens_info)) + .with_include_protocols(Some(req.include_protocols)) + .with_include_gas(Some(req.include_gas)) + .with_connector_tokens(req.connector_tokens) + .with_excluded_protocols(req.excluded_protocols) + .with_permit(req.permit) + .with_compatibility(req.compatibility) + .with_receiver(req.receiver) + .with_referrer(req.referrer) + .with_disable_estimate(req.disable_estimate) + .with_allow_partial_fill(req.allow_partial_fill) + .with_use_permit2(req.use_permit2) + .build_query_params() + .mm_err(|api_err| ApiIntegrationRpcError::from_api_error(api_err, Some(base.decimals())))?; + let swap_with_tx = ApiClient::new(ctx) + .mm_err(|api_err| ApiIntegrationRpcError::from_api_error(api_err, Some(base.decimals())))? + .call_swap_api( + base.chain_id(), + ApiClient::get_swap_method().to_owned(), + Some(query_params), + ) + .await + .mm_err(|api_err| ApiIntegrationRpcError::from_api_error(api_err, Some(base.decimals())))?; // use 'base' as amount in errors is in the src coin + ClassicSwapResponse::from_api_classic_swap_data(swap_with_tx, base.decimals()) // use 'base' as we spend in the src coin + .mm_err(|err| ApiIntegrationRpcError::ApiDataError(err.to_string())) +} + +/// "1inch_v6_0_classic_swap_liquidity_sources" rpc implementation. +/// Returns list of DEX available for routing with the 1inch Aggregation contract +pub async fn one_inch_v6_0_classic_swap_liquidity_sources_rpc( + ctx: MmArc, + req: ClassicSwapLiquiditySourcesRequest, +) -> MmResult { + let response: ProtocolsResponse = ApiClient::new(ctx) + .mm_err(|api_err| ApiIntegrationRpcError::from_api_error(api_err, None))? + .call_swap_api(req.chain_id, ApiClient::get_liquidity_sources_method().to_owned(), None) + .await + .mm_err(|api_err| ApiIntegrationRpcError::from_api_error(api_err, None))?; + Ok(ClassicSwapLiquiditySourcesResponse { + protocols: response.protocols, + }) +} + +/// "1inch_classic_swap_tokens" rpc implementation. +/// Returns list of tokens available for 1inch classic swaps +pub async fn one_inch_v6_0_classic_swap_tokens_rpc( + ctx: MmArc, + req: ClassicSwapTokensRequest, +) -> MmResult { + let response: TokensResponse = ApiClient::new(ctx) + .mm_err(|api_err| ApiIntegrationRpcError::from_api_error(api_err, None))? + .call_swap_api(req.chain_id, ApiClient::get_tokens_method().to_owned(), None) + .await + .mm_err(|api_err| ApiIntegrationRpcError::from_api_error(api_err, None))?; + Ok(ClassicSwapTokensResponse { + tokens: response.tokens, + }) +} + +async fn get_coin_for_one_inch(ctx: &MmArc, ticker: &str) -> MmResult<(EthCoin, String), ApiIntegrationRpcError> { + let coin = match lp_coinfind_or_err(ctx, ticker).await? { + MmCoinEnum::EthCoin(coin) => coin, + _ => return Err(MmError::new(ApiIntegrationRpcError::CoinTypeError)), + }; + let contract = match coin.coin_type { + EthCoinType::Eth => ApiClient::eth_special_contract().to_owned(), + EthCoinType::Erc20 { token_addr, .. } => display_eth_address(&token_addr), + EthCoinType::Nft { .. } => return Err(MmError::new(ApiIntegrationRpcError::NftNotSupported)), + }; + Ok((coin, contract)) +} + +#[allow(clippy::result_large_err)] +fn api_supports_pair(base: &EthCoin, rel: &EthCoin) -> MmResult<(), ApiIntegrationRpcError> { + if !ApiClient::is_chain_supported(base.chain_id()) { + return MmError::err(ApiIntegrationRpcError::ChainNotSupported); + } + if base.chain_id() != rel.chain_id() { + return MmError::err(ApiIntegrationRpcError::DifferentChains); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use crate::rpc::lp_commands::one_inch::{rpcs::{one_inch_v6_0_classic_swap_create_rpc, + one_inch_v6_0_classic_swap_quote_rpc}, + types::{ClassicSwapCreateRequest, ClassicSwapQuoteRequest}}; + use coins::eth::EthCoin; + use coins_activation::platform_for_tests::init_platform_coin_with_tokens_loop; + use common::block_on; + use crypto::CryptoCtx; + use mm2_core::mm_ctx::MmCtxBuilder; + use mm2_number::{BigDecimal, MmNumber}; + use mocktopus::mocking::{MockResult, Mockable}; + use std::str::FromStr; + use trading_api::one_inch_api::{client::ApiClient, types::ClassicSwapData}; + + #[test] + fn test_classic_swap_response_conversion() { + let ticker_coin = "ETH".to_owned(); + let ticker_token = "JST".to_owned(); + let eth_conf = json!({ + "coin": ticker_coin, + "name": "ethereum", + "derivation_path": "m/44'/1'", + "chain_id": 1, + "protocol": { + "type": "ETH" + }, + "trezor_coin": "Ethereum" + }); + let jst_conf = json!({ + "coin": ticker_token, + "name": "jst", + "chain_id": 1, + "protocol": { + "type": "ERC20", + "protocol_data": { + "platform": "ETH", + "contract_address": "0x09d0d71FBC00D7CCF9CFf132f5E6825C88293F19" + } + }, + }); + + let conf = json!({ + "coins": [eth_conf, jst_conf], + "1inch_api": "https://api.1inch.dev" + }); + let ctx = MmCtxBuilder::new().with_conf(conf).into_mm_arc(); + CryptoCtx::init_with_iguana_passphrase(ctx.clone(), "123").unwrap(); + + block_on(init_platform_coin_with_tokens_loop::( + ctx.clone(), + serde_json::from_value(json!({ + "ticker": ticker_coin, + "rpc_mode": "Default", + "nodes": [ + {"url": "https://rpc2.sepolia.org"}, + {"url": "https://rpc.sepolia.org/"} + ], + "swap_contract_address": "0xeA6D65434A15377081495a9E7C5893543E7c32cB", + "erc20_tokens_requests": [{"ticker": ticker_token}], + "priv_key_policy": "ContextPrivKey" + })) + .unwrap(), + )) + .unwrap(); + + let response_quote_raw = json!({ + "dstAmount": "13", + "srcToken": { + "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "symbol": ticker_coin, + "name": "Ether", + "decimals": 18, + "eip2612": false, + "isFoT": false, + "logoURI": "https://tokens.1inch.io/0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee.png", + "tags": [ + "crosschain", + "GROUP:ETH", + "native", + "PEG:ETH" + ] + }, + "dstToken": { + "address": "0x1234567890123456789012345678901234567890", + "symbol": ticker_token, + "name": "Test just token", + "decimals": 6, + "eip2612": false, + "isFoT": false, + "logoURI": "https://example.org/0x1234567890123456789012345678901234567890.png", + "tags": [ + "crosschain", + "GROUP:JSTT", + "PEG:JST", + "tokens" + ] + }, + "protocols": [ + [ + [ + { + "name": "SUSHI", + "part": 100, + "fromTokenAddress": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "toTokenAddress": "0xf16e81dce15b08f326220742020379b855b87df9" + } + ], + [ + { + "name": "ONE_INCH_LIMIT_ORDER_V3", + "part": 100, + "fromTokenAddress": "0xf16e81dce15b08f326220742020379b855b87df9", + "toTokenAddress": "0xdac17f958d2ee523a2206206994597c13d831ec7" + } + ] + ] + ], + "gas": 452704 + }); + + let response_create_raw = json!({ + "dstAmount": "13", + "tx": { + "from": "0x590559f6fb7720f24ff3e2fccf6015b466e9c92c", + "to": "0x111111125421ca6dc452d289314280a0f8842a65", + "data": "0x07ed23790000000000000000000000005f515f6c524b18ca30f7783fb58dd4be2e9904ec000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000005f515f6c524b18ca30f7783fb58dd4be2e9904ec000000000000000000000000590559f6fb7720f24ff3e2fccf6015b466e9c92c0000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000648e8755f7ac30b5e4fa3f9c00e2cb6667501797b8bc01a7a367a4b2889ca6a05d9c31a31a781c12a4c3bdfc2ef1e02942e388b6565989ebe860bd67925bda74fbe0000000000000000000000000000000000000000000000000005ea0005bc00a007e5c0d200000000000000000000000000000000059800057e00018500009500001a4041c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2d0e30db00c20c02aaa39b223fe8d0a0e5c4f27ead9083c756cc27b73644935b8e68019ac6356c40661e1bc3158606ae4071118002dc6c07b73644935b8e68019ac6356c40661e1bc3158600000000000000000000000000000000000000000000000000294932ccadc9c58c02aaa39b223fe8d0a0e5c4f27ead9083c756cc251204dff5675ecff96b565ba3804dd4a63799ccba406761d38e5ddf6ccf6cf7c55759d5210750b5d60f30044e331d039000000000000000000000000761d38e5ddf6ccf6cf7c55759d5210750b5d60f3000000000000000000000000111111111117dc0aa78b770fa6a738034120c302000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002f8a744a79be00000000000000000000000042f527f50f16a103b6ccab48bccca214500c10210000000000000000000000005f515f6c524b18ca30f7783fb58dd4be2e9904ec00a0860a32ec00000000000000000000000000000000000000000000000000003005635d54300003d05120ead050515e10fdb3540ccd6f8236c46790508a76111111111117dc0aa78b770fa6a738034120c30200c4e525b10b000000000000000000000000000000000000000000000000000000000000002000000000000000000000000022b1a53ac4be63cdc1f47c99572290eff1edd8020000000000000000000000006a32cc044dd6359c27bb66e7b02dce6dd0fda2470000000000000000000000005f515f6c524b18ca30f7783fb58dd4be2e9904ec000000000000000000000000111111111117dc0aa78b770fa6a738034120c302000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003005635d5430000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000067138e8c00000000000000000000000000000000000000000000000000030fb9b1525d8185f8d63fbcbe42e5999263c349cb5d81000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000026000000000000000000000000067297ee4eb097e072b4ab6f1620268061ae8046400000000000000000000000060cba82ddbf4b5ddcd4398cdd05354c6a790c309000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041d26038ef66344af785ff342b86db3da06c4cc6a62f0ca80ffd78affc0a95ccad44e814acebb1deda729bbfe3050bec14a47af487cc1cadc75f43db2d073016c31c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041a66cd52a747c5f60b9db637ffe30d0e413ec87858101832b4c5c1ae154bf247f3717c8ed4133e276ddf68d43a827f280863c91d6c42bc6ad1ec7083b2315b6fd1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020d6bdbf78dac17f958d2ee523a2206206994597c13d831ec780a06c4eca27dac17f958d2ee523a2206206994597c13d831ec7111111125421ca6dc452d289314280a0f8842a65000000000000000000000000000000000000000000000000c095c0a2", + "value": "10000000", + "gas": 721429, + "gasPrice": "9525172167" + }, + "srcToken": { + "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "symbol": ticker_coin, + "name": "Ether", + "decimals": 18, + "eip2612": false, + "isFoT": false, + "logoURI": "https://tokens.1inch.io/0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee.png", + "tags": [ + "crosschain", + "GROUP:ETH", + "native", + "PEG:ETH" + ] + }, + "dstToken": { + "address": "0x1234567890123456789012345678901234567890", + "symbol": ticker_token, + "name": "Just Token", + "decimals": 6, + "eip2612": false, + "isFoT": false, + "logoURI": "https://tokens.1inch.io/0x1234567890123456789012345678901234567890.png", + "tags": [ + "crosschain", + "GROUP:USDT", + "PEG:USD", + "tokens" + ] + }, + "protocols": [ + [ + [ + { + "name": "UNISWAP_V2", + "part": 100, + "fromTokenAddress": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "toTokenAddress": "0x761d38e5ddf6ccf6cf7c55759d5210750b5d60f3" + } + ], + [ + { + "name": "ONE_INCH_LP_1_1", + "part": 100, + "fromTokenAddress": "0x761d38e5ddf6ccf6cf7c55759d5210750b5d60f3", + "toTokenAddress": "0x111111111117dc0aa78b770fa6a738034120c302" + } + ], + [ + { + "name": "PMM11", + "part": 100, + "fromTokenAddress": "0x111111111117dc0aa78b770fa6a738034120c302", + "toTokenAddress": "0xdac17f958d2ee523a2206206994597c13d831ec7" + } + ] + ] + ] + }); + + let quote_req = ClassicSwapQuoteRequest { + base: ticker_coin.clone(), + rel: ticker_token.clone(), + amount: MmNumber::from("1.0"), + fee: None, + protocols: None, + gas_price: None, + complexity_level: None, + parts: None, + main_route_parts: None, + gas_limit: None, + include_tokens_info: true, + include_protocols: true, + include_gas: true, + connector_tokens: None, + }; + + let create_req = ClassicSwapCreateRequest { + base: ticker_coin.clone(), + rel: ticker_token.clone(), + amount: MmNumber::from("1.0"), + fee: None, + protocols: None, + gas_price: None, + complexity_level: None, + parts: None, + main_route_parts: None, + gas_limit: None, + include_tokens_info: true, + include_protocols: true, + include_gas: true, + connector_tokens: None, + slippage: 0.0, + excluded_protocols: None, + permit: None, + compatibility: None, + receiver: None, + referrer: None, + disable_estimate: None, + allow_partial_fill: None, + use_permit2: None, + }; + + ApiClient::call_swap_api::.mock_safe(move |_, _, _, _| { + let response_quote_raw = response_quote_raw.clone(); + MockResult::Return(Box::pin(async move { + Ok(serde_json::from_value::(response_quote_raw).unwrap()) + })) + }); + + let quote_response = block_on(one_inch_v6_0_classic_swap_quote_rpc(ctx.clone(), quote_req)).unwrap(); + assert_eq!( + quote_response.dst_amount.amount, + BigDecimal::from_str("0.000000000000000013").unwrap() + ); + assert_eq!(quote_response.src_token.as_ref().unwrap().symbol, ticker_coin); + assert_eq!(quote_response.src_token.as_ref().unwrap().decimals, 18); + assert_eq!(quote_response.dst_token.as_ref().unwrap().symbol, ticker_token); + assert_eq!(quote_response.dst_token.as_ref().unwrap().decimals, 6); + assert_eq!(quote_response.gas.unwrap(), 452704_u128); + + ApiClient::call_swap_api::.mock_safe(move |_, _, _, _| { + let response_create_raw = response_create_raw.clone(); + MockResult::Return(Box::pin(async move { + Ok(serde_json::from_value::(response_create_raw).unwrap()) + })) + }); + let create_response = block_on(one_inch_v6_0_classic_swap_create_rpc(ctx, create_req)).unwrap(); + assert_eq!( + create_response.dst_amount.amount, + BigDecimal::from_str("0.000000000000000013").unwrap() + ); + assert_eq!(create_response.src_token.as_ref().unwrap().symbol, ticker_coin); + assert_eq!(create_response.src_token.as_ref().unwrap().decimals, 18); + assert_eq!(create_response.dst_token.as_ref().unwrap().symbol, ticker_token); + assert_eq!(create_response.dst_token.as_ref().unwrap().decimals, 6); + assert_eq!(create_response.tx.as_ref().unwrap().data.len(), 1960); + } +} diff --git a/mm2src/mm2_main/src/rpc/lp_commands/one_inch/types.rs b/mm2src/mm2_main/src/rpc/lp_commands/one_inch/types.rs new file mode 100644 index 0000000000..202eb0dcf2 --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/one_inch/types.rs @@ -0,0 +1,213 @@ +use crate::rpc::lp_commands::one_inch::errors::FromApiValueError; +use coins::eth::{u256_to_big_decimal, wei_to_gwei_decimal}; +use common::true_f; +use ethereum_types::{Address, U256}; +use mm2_err_handle::prelude::*; +use mm2_number::{construct_detailed, BigDecimal, MmNumber}; +use rpc::v1::types::Bytes as BytesJson; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use trading_api::one_inch_api::{self, + types::{ProtocolImage, ProtocolInfo, TokenInfo}}; + +construct_detailed!(DetailedAmount, amount); + +#[derive(Clone, Debug, Deserialize)] +pub struct AggregationContractRequest {} + +/// Request to get quote for 1inch classic swap. +/// See 1inch docs for more details: https://portal.1inch.dev/documentation/apis/swap/classic-swap/Parameter%20Descriptions/quote_params +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ClassicSwapQuoteRequest { + /// Base coin ticker + pub base: String, + /// Rel coin ticker + pub rel: String, + /// Swap amount in coins (with fraction) + pub amount: MmNumber, + /// Partner fee, percentage of src token amount will be sent to referrer address, min: 0; max: 3. + /// Should be the same for quote and swap rpc. Default is 0 + pub fee: Option, + /// Specify liquidity sources + /// e.g.: &protocols=WETH,CURVE,BALANCER,...,ZRX + /// (by default - all used) + pub protocols: Option, + /// Network price per gas, in Gwei for this rpc. + /// 1inch takes in account gas expenses to determine exchange route. Should be the same for a quote and swap. + /// If not set the 'fast' network gas price will be used + pub gas_price: Option, + /// Maximum number of token-connectors to be used in a transaction, min: 0; max: 3; default: 2 + pub complexity_level: Option, + /// Limit maximum number of parts each main route parts can be split into. + /// Should be the same for a quote and swap. Default: 20; max: 100 + pub parts: Option, + /// Limit maximum number of main route parts. Should be the same for a quote and swap. Default: 20; max: 50; + pub main_route_parts: Option, + /// Maximum amount of gas for a swap. + /// Should be the same for a quote and swap. Default: 11500000; max: 11500000 + pub gas_limit: Option, + /// Return fromToken and toToken info in response (default is true) + #[serde(default = "true_f")] + pub include_tokens_info: bool, + /// Return used swap protocols in response (default is true) + #[serde(default = "true_f")] + pub include_protocols: bool, + /// Include estimated gas in return value (default is true) + #[serde(default = "true_f")] + pub include_gas: bool, + /// Token-connectors can be specified via this parameter. If not set, default token-connectors will be used + pub connector_tokens: Option, +} + +/// Request to create transaction for 1inch classic swap. +/// See 1inch docs for more details: https://portal.1inch.dev/documentation/apis/swap/classic-swap/Parameter%20Descriptions/swap_params +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ClassicSwapCreateRequest { + /// Base coin ticker + pub base: String, + /// Rel coin ticker + pub rel: String, + /// Swap amount in coins (with fraction) + pub amount: MmNumber, + /// Allowed slippage, min: 0; max: 50 + pub slippage: f32, + /// Partner fee, percentage of src token amount will be sent to referrer address, min: 0; max: 3. + /// Should be the same for quote and swap rpc. Default is 0 + pub fee: Option, + /// Specify liquidity sources + /// e.g.: &protocols=WETH,CURVE,BALANCER,...,ZRX + /// (by default - all used) + pub protocols: Option, + /// Network price per gas, in Gwei for this rpc. + /// 1inch takes in account gas expenses to determine exchange route. Should be the same for a quote and swap. + /// If not set the 'fast' network gas price will be used + pub gas_price: Option, + /// Maximum number of token-connectors to be used in a transaction, min: 0; max: 3; default: 2 + pub complexity_level: Option, + /// Limit maximum number of parts each main route parts can be split into. + /// Should be the same for a quote and swap. Default: 20; max: 100 + pub parts: Option, + /// Limit maximum number of main route parts. Should be the same for a quote and swap. Default: 20; max: 50; + pub main_route_parts: Option, + /// Maximum amount of gas for a swap. + /// Should be the same for a quote and swap. Default: 11500000; max: 11500000 + pub gas_limit: Option, + /// Return fromToken and toToken info in response (default is true) + #[serde(default = "true_f")] + pub include_tokens_info: bool, + /// Return used swap protocols in response (default is true) + #[serde(default = "true_f")] + pub include_protocols: bool, + /// Include estimated gas in response (default is true) + #[serde(default = "true_f")] + pub include_gas: bool, + /// Token-connectors can be specified via this parameter. If not set, default token-connectors will be used + pub connector_tokens: Option, + /// Excluded supported liquidity sources. Should be the same for a quote and swap, max: 5 + pub excluded_protocols: Option, + /// Used according https://eips.ethereum.org/EIPS/eip-2612 + pub permit: Option, + /// Exclude the Unoswap method + pub compatibility: Option, + /// This address will receive funds after the swap. By default same address as 'my address' + pub receiver: Option, + /// Address to receive the partner fee. Must be set explicitly if fee is also set + pub referrer: Option, + /// if true, disable most of the checks, default: false + pub disable_estimate: Option, + /// if true, the algorithm can cancel part of the route, if the rate has become less attractive. + /// Unswapped tokens will return to 'my address'. Default: true + pub allow_partial_fill: Option, + /// Enable this flag for auto approval by Permit2 contract if you did an approval to Uniswap Permit2 smart contract for this token. + /// Default is false + pub use_permit2: Option, +} + +/// Response for both classic swap quote or create swap calls +#[derive(Serialize, Debug)] +pub struct ClassicSwapResponse { + /// Destination token amount, in coins (with fraction) + pub dst_amount: DetailedAmount, + /// Source (base) token info + #[serde(skip_serializing_if = "Option::is_none")] + pub src_token: Option, + /// Destination (rel) token info + #[serde(skip_serializing_if = "Option::is_none")] + pub dst_token: Option, + /// Used liquidity sources + #[serde(skip_serializing_if = "Option::is_none")] + pub protocols: Option>>>, + /// Swap tx fields (returned only for create swap rpc) + #[serde(skip_serializing_if = "Option::is_none")] + pub tx: Option, + /// Estimated (returned only for quote rpc) + pub gas: Option, +} + +impl ClassicSwapResponse { + pub(crate) fn from_api_classic_swap_data( + data: one_inch_api::types::ClassicSwapData, + decimals: u8, + ) -> MmResult { + Ok(Self { + dst_amount: MmNumber::from(u256_to_big_decimal(U256::from_dec_str(&data.dst_amount)?, decimals)?).into(), + src_token: data.src_token, + dst_token: data.dst_token, + protocols: data.protocols, + tx: data + .tx + .map(|tx| TxFields::from_api_tx_fields(tx, decimals)) + .transpose()?, + gas: data.gas, + }) + } +} + +#[derive(Serialize, Debug)] +pub struct TxFields { + pub from: Address, + pub to: Address, + pub data: BytesJson, + pub value: BigDecimal, + /// Estimated gas price in gwei + pub gas_price: BigDecimal, + pub gas: u128, // TODO: in eth EthTxFeeDetails rpc we use u64. Better have identical u128 everywhere +} + +impl TxFields { + pub(crate) fn from_api_tx_fields( + tx_fields: one_inch_api::types::TxFields, + decimals: u8, + ) -> MmResult { + Ok(Self { + from: tx_fields.from, + to: tx_fields.to, + data: BytesJson::from(hex::decode(str_strip_0x!(tx_fields.data.as_str()))?), + value: u256_to_big_decimal(U256::from_dec_str(&tx_fields.value)?, decimals)?, + gas_price: wei_to_gwei_decimal(U256::from_dec_str(&tx_fields.gas_price)?)?, + gas: tx_fields.gas, + }) + } +} + +#[derive(Deserialize)] +pub struct ClassicSwapLiquiditySourcesRequest { + pub chain_id: u64, +} + +#[derive(Serialize)] +pub struct ClassicSwapLiquiditySourcesResponse { + pub protocols: Vec, +} + +#[derive(Deserialize)] +pub struct ClassicSwapTokensRequest { + pub chain_id: u64, +} + +#[derive(Serialize)] +pub struct ClassicSwapTokensResponse { + pub tokens: HashMap, +} diff --git a/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs b/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs new file mode 100644 index 0000000000..f5a5a95063 --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs @@ -0,0 +1,48 @@ +use common::HttpStatusCode; +use crypto::{CryptoCtx, CryptoCtxError}; +use derive_more::Display; +use http::StatusCode; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; +use rpc::v1::types::H160 as H160Json; +use serde_json::Value as Json; + +pub type GetPublicKeyRpcResult = Result>; + +#[derive(Serialize, Display, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum GetPublicKeyError { + Internal(String), +} + +impl From for GetPublicKeyError { + fn from(_: CryptoCtxError) -> Self { GetPublicKeyError::Internal("public_key not available".to_string()) } +} + +#[derive(Serialize)] +pub struct GetPublicKeyResponse { + public_key: String, +} + +impl HttpStatusCode for GetPublicKeyError { + fn status_code(&self) -> StatusCode { + match self { + GetPublicKeyError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +pub async fn get_public_key(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { + let public_key = CryptoCtx::from_ctx(&ctx)?.mm2_internal_pubkey().to_string(); + Ok(GetPublicKeyResponse { public_key }) +} + +#[derive(Serialize)] +pub struct GetPublicKeyHashResponse { + public_key_hash: H160Json, +} + +pub async fn get_public_key_hash(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { + let public_key_hash = ctx.rmd160().to_owned().into(); + Ok(GetPublicKeyHashResponse { public_key_hash }) +} diff --git a/mm2src/mm2_main/src/rpc/lp_commands/tokens.rs b/mm2src/mm2_main/src/rpc/lp_commands/tokens.rs new file mode 100644 index 0000000000..78697530c1 --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/tokens.rs @@ -0,0 +1,173 @@ +//! This source file is for RPCs specific for EVM platform +use coins::eth::erc20::{get_erc20_ticker_by_contract_address, get_erc20_token_info, Erc20TokenInfo}; +use coins::eth::valid_addr_from_str; +use coins::eth::{u256_to_big_decimal, wei_from_big_decimal, EthCoin, Web3RpcError}; +use coins::{lp_coinfind_or_err, CoinFindError, CoinProtocol, MmCoin, MmCoinEnum, NumConversError, Transaction, + TransactionErr}; +use common::HttpStatusCode; +use derive_more::Display; +use enum_derives::EnumFromStringify; +use ethereum_types::Address as EthAddress; +use futures::compat::Future01CompatExt; +use http::StatusCode; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::{map_to_mm::MapToMmResult, mm_error::MmError, prelude::MmResult}; +use mm2_number::BigDecimal; + +#[derive(Deserialize)] +pub struct TokenInfoRequest { + protocol: CoinProtocol, +} + +#[derive(Serialize)] +#[serde(tag = "type", content = "info")] +pub enum TokenInfo { + ERC20(Erc20TokenInfo), +} + +#[derive(Serialize)] +pub struct TokenInfoResponse { + #[serde(skip_serializing_if = "Option::is_none")] + config_ticker: Option, + #[serde(flatten)] + info: TokenInfo, +} + +#[derive(Debug, Deserialize, Display, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum TokenInfoError { + #[display(fmt = "No such coin {}", coin)] + NoSuchCoin { coin: String }, + #[display(fmt = "Custom tokens are not supported for {} protocol yet!", protocol)] + UnsupportedTokenProtocol { protocol: String }, + #[display(fmt = "Invalid request {}", _0)] + InvalidRequest(String), + #[display(fmt = "Error retrieving token info {}", _0)] + RetrieveInfoError(String), +} + +impl HttpStatusCode for TokenInfoError { + fn status_code(&self) -> StatusCode { + match self { + TokenInfoError::NoSuchCoin { .. } => StatusCode::NOT_FOUND, + TokenInfoError::UnsupportedTokenProtocol { .. } | TokenInfoError::InvalidRequest(_) => { + StatusCode::BAD_REQUEST + }, + TokenInfoError::RetrieveInfoError(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +impl From for TokenInfoError { + fn from(e: CoinFindError) -> Self { + match e { + CoinFindError::NoSuchCoin { coin } => TokenInfoError::NoSuchCoin { coin }, + } + } +} + +pub async fn get_token_info(ctx: MmArc, req: TokenInfoRequest) -> MmResult { + // Check that the protocol is a token protocol + let platform = req.protocol.platform().ok_or(TokenInfoError::InvalidRequest(format!( + "Protocol '{:?}' is not a token protocol", + req.protocol + )))?; + // Platform coin should be activated + let platform_coin = lp_coinfind_or_err(&ctx, platform).await?; + match platform_coin { + MmCoinEnum::EthCoin(eth_coin) => { + let contract_address_str = + req.protocol + .contract_address() + .ok_or(TokenInfoError::UnsupportedTokenProtocol { + protocol: platform.to_string(), + })?; + let contract_address = valid_addr_from_str(contract_address_str).map_to_mm(|e| { + let error = format!("Invalid contract address: {}", e); + TokenInfoError::InvalidRequest(error) + })?; + + let config_ticker = get_erc20_ticker_by_contract_address(&ctx, platform, contract_address_str); + let token_info = get_erc20_token_info(ð_coin, contract_address) + .await + .map_to_mm(TokenInfoError::RetrieveInfoError)?; + Ok(TokenInfoResponse { + config_ticker, + info: TokenInfo::ERC20(token_info), + }) + }, + _ => MmError::err(TokenInfoError::UnsupportedTokenProtocol { + protocol: platform.to_string(), + }), + } +} + +#[derive(Debug, Deserialize, Display, EnumFromStringify, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum Erc20CallError { + #[display(fmt = "No such coin {}", coin)] + NoSuchCoin { coin: String }, + #[display(fmt = "Coin not supported {}", coin)] + CoinNotSupported { coin: String }, + #[from_stringify("NumConversError")] + #[display(fmt = "Invalid param: {}", _0)] + InvalidParam(String), + #[from_stringify("TransactionErr")] + #[display(fmt = "Transaction error {}", _0)] + TransactionError(String), + #[from_stringify("Web3RpcError")] + #[display(fmt = "Web3 RPC error {}", _0)] + Web3RpcError(String), +} + +impl HttpStatusCode for Erc20CallError { + fn status_code(&self) -> StatusCode { + match self { + Erc20CallError::NoSuchCoin { .. } + | Erc20CallError::CoinNotSupported { .. } + | Erc20CallError::InvalidParam(_) => StatusCode::BAD_REQUEST, + Erc20CallError::TransactionError(_) | Erc20CallError::Web3RpcError(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct Erc20AllowanceRequest { + coin: String, + spender: EthAddress, +} + +/// Call allowance method for ERC20 tokens (see https://eips.ethereum.org/EIPS/eip-20#approve). +/// Returns BigDecimal allowance value. +pub async fn get_token_allowance_rpc(ctx: MmArc, req: Erc20AllowanceRequest) -> MmResult { + let eth_coin = find_erc20_eth_coin(&ctx, &req.coin).await?; + let wei = eth_coin.allowance(req.spender).compat().await?; + let amount = u256_to_big_decimal(wei, eth_coin.decimals())?; + Ok(amount) +} + +#[derive(Debug, Deserialize)] +pub struct Erc20ApproveRequest { + coin: String, + spender: EthAddress, + amount: BigDecimal, +} + +/// Call approve method for ERC20 tokens (see https://eips.ethereum.org/EIPS/eip-20#allowance). +/// Returns approval transaction hash. +pub async fn approve_token_rpc(ctx: MmArc, req: Erc20ApproveRequest) -> MmResult { + let eth_coin = find_erc20_eth_coin(&ctx, &req.coin).await?; + let amount = wei_from_big_decimal(&req.amount, eth_coin.decimals())?; + let tx = eth_coin.approve(req.spender, amount).compat().await?; + Ok(format!("0x{:02x}", tx.tx_hash_as_bytes())) +} + +async fn find_erc20_eth_coin(ctx: &MmArc, coin: &str) -> Result> { + match lp_coinfind_or_err(ctx, coin).await { + Ok(MmCoinEnum::EthCoin(eth_coin)) => Ok(eth_coin), + Ok(_) => Err(MmError::new(Erc20CallError::CoinNotSupported { + coin: coin.to_string(), + })), + Err(_) => Err(MmError::new(Erc20CallError::NoSuchCoin { coin: coin.to_string() })), + } +} diff --git a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs b/mm2src/mm2_main/src/rpc/lp_commands/trezor.rs similarity index 52% rename from mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs rename to mm2src/mm2_main/src/rpc/lp_commands/trezor.rs index ae992c6d3e..16698eb3cc 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/trezor.rs @@ -1,63 +1,9 @@ use common::HttpStatusCode; use crypto::{CryptoCtx, CryptoCtxError, HwConnectionStatus, HwPubkey}; -use derive_more::Display; use http::StatusCode; use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::prelude::*; -use rpc::v1::types::H160 as H160Json; -use serde_json::Value as Json; - -pub type GetPublicKeyRpcResult = Result>; -pub type GetSharedDbIdResult = Result>; -pub type GetSharedDbIdError = GetPublicKeyError; - -#[derive(Serialize, Display, SerializeErrorType)] -#[serde(tag = "error_type", content = "error_data")] -pub enum GetPublicKeyError { - Internal(String), -} - -impl From for GetPublicKeyError { - fn from(_: CryptoCtxError) -> Self { GetPublicKeyError::Internal("public_key not available".to_string()) } -} - -#[derive(Serialize)] -pub struct GetPublicKeyResponse { - public_key: String, -} - -impl HttpStatusCode for GetPublicKeyError { - fn status_code(&self) -> StatusCode { - match self { - GetPublicKeyError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, - } - } -} - -pub async fn get_public_key(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { - let public_key = CryptoCtx::from_ctx(&ctx)?.mm2_internal_pubkey().to_string(); - Ok(GetPublicKeyResponse { public_key }) -} - -#[derive(Serialize)] -pub struct GetPublicKeyHashResponse { - public_key_hash: H160Json, -} - -pub async fn get_public_key_hash(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { - let public_key_hash = ctx.rmd160().to_owned().into(); - Ok(GetPublicKeyHashResponse { public_key_hash }) -} - -#[derive(Serialize)] -pub struct GetSharedDbIdResponse { - shared_db_id: H160Json, -} - -pub async fn get_shared_db_id(ctx: MmArc, _req: Json) -> GetSharedDbIdResult { - let shared_db_id = ctx.shared_db_id().to_owned().into(); - Ok(GetSharedDbIdResponse { shared_db_id }) -} +use mm2_err_handle::mm_error::{MmError, MmResult}; +use mm2_err_handle::or_mm_error::OrMmError; #[derive(Serialize, Display, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] diff --git a/mm2src/mm2_main/src/rpc/rate_limiter.rs b/mm2src/mm2_main/src/rpc/rate_limiter.rs index 9f07be2b46..aa68cee4a8 100644 --- a/mm2src/mm2_main/src/rpc/rate_limiter.rs +++ b/mm2src/mm2_main/src/rpc/rate_limiter.rs @@ -1,4 +1,4 @@ -use crate::mm2::rpc::DispatcherError; +use crate::rpc::DispatcherError; use derive_more::Display; use futures::lock::Mutex as AsyncMutex; use mm2_core::mm_ctx::from_ctx; diff --git a/mm2src/mm2_main/src/wasm_tests.rs b/mm2src/mm2_main/src/wasm_tests.rs index a43b948ac8..bd24bf4a4c 100644 --- a/mm2src/mm2_main/src/wasm_tests.rs +++ b/mm2src/mm2_main/src/wasm_tests.rs @@ -1,14 +1,14 @@ -use crate::mm2::lp_init; -use common::executor::{spawn, Timer}; +use crate::lp_init; +use common::executor::{spawn, spawn_abortable, spawn_local_abortable, AbortOnDropHandle, Timer}; use common::log::wasm_log::register_wasm_log; use mm2_core::mm_ctx::MmArc; use mm2_number::BigDecimal; use mm2_rpc::data::legacy::OrderbookResponse; use mm2_test_helpers::electrums::{doc_electrums, marty_electrums}; use mm2_test_helpers::for_tests::{check_recent_swaps, enable_electrum_json, enable_utxo_v2_electrum, - enable_z_coin_light, morty_conf, pirate_conf, rick_conf, start_swaps, - test_qrc20_history_impl, wait_for_swaps_finish_and_check_status, MarketMakerIt, - Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, ARRR, MORTY, + enable_z_coin_light, get_wallet_names, morty_conf, pirate_conf, rick_conf, + start_swaps, test_qrc20_history_impl, wait_for_swaps_finish_and_check_status, + MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, ARRR, MORTY, PIRATE_ELECTRUMS, PIRATE_LIGHTWALLETD_URLS, RICK}; use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::{Bip44Chain, EnableCoinBalance, HDAccountAddressId}; @@ -16,6 +16,7 @@ use serde_json::json; use wasm_bindgen_test::wasm_bindgen_test; const PIRATE_TEST_BALANCE_SEED: &str = "pirate test seed"; +const STOP_TIMEOUT_MS: u64 = 1000; /// Starts the WASM version of MM. fn wasm_start(ctx: MmArc) { @@ -26,13 +27,7 @@ fn wasm_start(ctx: MmArc) { /// This function runs Alice and Bob nodes, activates coins, starts swaps, /// and then immediately stops the nodes to check if `MmArc` is dropped in a short period. -async fn test_mm2_stops_impl( - pairs: &[(&'static str, &'static str)], - maker_price: f64, - taker_price: f64, - volume: f64, - stop_timeout_ms: u64, -) { +async fn test_mm2_stops_impl(pairs: &[(&'static str, &'static str)], maker_price: f64, taker_price: f64, volume: f64) { let coins = json!([rick_conf(), morty_conf()]); let bob_passphrase = get_passphrase!(".env.seed", "BOB_PASSPHRASE").unwrap(); @@ -69,20 +64,18 @@ async fn test_mm2_stops_impl( start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume).await; mm_alice - .stop_and_wait_for_ctx_is_dropped(stop_timeout_ms) + .stop_and_wait_for_ctx_is_dropped(STOP_TIMEOUT_MS) .await .unwrap(); - mm_bob.stop_and_wait_for_ctx_is_dropped(stop_timeout_ms).await.unwrap(); + mm_bob.stop_and_wait_for_ctx_is_dropped(STOP_TIMEOUT_MS).await.unwrap(); } #[wasm_bindgen_test] async fn test_mm2_stops_immediately() { - const STOP_TIMEOUT_MS: u64 = 1000; - register_wasm_log(); let pairs: &[_] = &[("RICK", "MORTY")]; - test_mm2_stops_impl(pairs, 1., 1., 0.0001, STOP_TIMEOUT_MS).await; + test_mm2_stops_impl(pairs, 1., 1., 0.0001).await; } #[wasm_bindgen_test] @@ -147,8 +140,6 @@ async fn trade_base_rel_electrum( assert_eq!(0, bob_orderbook.asks.len(), "{} {} asks must be empty", base, rel); } - const STOP_TIMEOUT_MS: u64 = 1000; - mm_bob.stop_and_wait_for_ctx_is_dropped(STOP_TIMEOUT_MS).await.unwrap(); mm_alice .stop_and_wait_for_ctx_is_dropped(STOP_TIMEOUT_MS) @@ -266,3 +257,46 @@ async fn activate_z_coin_light() { }; assert_eq!(balance.balance.spendable, BigDecimal::default()); } + +#[wasm_bindgen_test] +async fn test_get_wallet_names() { + const DB_NAMESPACE_NUM: u64 = 1; + + let coins = json!([]); + + // Initialize the first wallet with a specific name + let wallet_1 = Mm2TestConf::seednode_with_wallet_name(&coins, "wallet_1", "pass"); + let mm_wallet_1 = + MarketMakerIt::start_with_db(wallet_1.conf, wallet_1.rpc_password, Some(wasm_start), DB_NAMESPACE_NUM) + .await + .unwrap(); + + // Retrieve and verify the wallet names for the first wallet + let get_wallet_names_1 = get_wallet_names(&mm_wallet_1).await; + assert_eq!(get_wallet_names_1.wallet_names, vec!["wallet_1"]); + assert_eq!(get_wallet_names_1.activated_wallet.unwrap(), "wallet_1"); + + // Stop the first wallet before starting the second one + mm_wallet_1 + .stop_and_wait_for_ctx_is_dropped(STOP_TIMEOUT_MS) + .await + .unwrap(); + + // Initialize the second wallet with a different name + let wallet_2 = Mm2TestConf::seednode_with_wallet_name(&coins, "wallet_2", "pass"); + let mm_wallet_2 = + MarketMakerIt::start_with_db(wallet_2.conf, wallet_2.rpc_password, Some(wasm_start), DB_NAMESPACE_NUM) + .await + .unwrap(); + + // Retrieve and verify the wallet names for the second wallet + let get_wallet_names_2 = get_wallet_names(&mm_wallet_2).await; + assert_eq!(get_wallet_names_2.wallet_names, vec!["wallet_1", "wallet_2"]); + assert_eq!(get_wallet_names_2.activated_wallet.unwrap(), "wallet_2"); + + // Stop the second wallet + mm_wallet_2 + .stop_and_wait_for_ctx_is_dropped(STOP_TIMEOUT_MS) + .await + .unwrap(); +} diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs index ecb57100fd..3050f22826 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs @@ -1,4 +1,4 @@ -pub use common::{block_on, now_ms, now_sec, wait_until_ms, wait_until_sec}; +pub use common::{block_on, block_on_f01, now_ms, now_sec, wait_until_ms, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; pub use mm2_number::MmNumber; use mm2_rpc::data::legacy::BalanceResponse; pub use mm2_test_helpers::for_tests::{check_my_swap_status, check_recent_swaps, enable_eth_coin, enable_native, @@ -7,8 +7,7 @@ pub use mm2_test_helpers::for_tests::{check_my_swap_status, check_recent_swaps, MAKER_ERROR_EVENTS, MAKER_SUCCESS_EVENTS, TAKER_ERROR_EVENTS, TAKER_SUCCESS_EVENTS}; -use super::eth_docker_tests::{erc20_contract_checksum, fill_eth, fill_eth_erc20_with_private_key, geth_account, - swap_contract}; +use super::eth_docker_tests::{erc20_contract_checksum, fill_eth, fill_eth_erc20_with_private_key, swap_contract}; use bitcrypto::{dhash160, ChecksumType}; use chain::TransactionOutput; use coins::eth::addr_from_raw_pubkey; @@ -28,7 +27,6 @@ use crypto::Secp256k1Secret; use ethabi::Token; use ethereum_types::{H160 as H160Eth, U256}; use futures::TryFutureExt; -use futures01::Future; use http::StatusCode; use keys::{Address, AddressBuilder, AddressHashEnum, AddressPrefix, KeyPair, NetworkAddressPrefixes, NetworkPrefix as CashAddrPrefix}; @@ -41,18 +39,16 @@ use script::Builder; use secp256k1::Secp256k1; pub use secp256k1::{PublicKey, SecretKey}; use serde_json::{self as json, Value as Json}; -pub use std::env; -use std::path::PathBuf; -use std::process::Command; -use std::sync::Mutex; -pub use std::thread; -use std::time::Duration; -use testcontainers::clients::Cli; -use testcontainers::core::WaitFor; -use testcontainers::{Container, GenericImage, RunnableImage}; -use web3::transports::Http; +use std::process::{Command, Stdio}; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +use std::str::FromStr; +pub use std::{env, thread}; +use std::{path::PathBuf, sync::Mutex, time::Duration}; +use testcontainers::{clients::Cli, core::WaitFor, Container, GenericImage, RunnableImage}; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +use web3::types::Address as EthAddress; use web3::types::{BlockId, BlockNumber, TransactionRequest}; -use web3::Web3; +use web3::{transports::Http, Web3}; lazy_static! { static ref MY_COIN_LOCK: Mutex<()> = Mutex::new(()); @@ -64,12 +60,25 @@ lazy_static! { // Due to the SLP protocol limitations only 19 outputs (18 + change) can be sent in one transaction, which is sufficient for now though. // Supply more privkeys when 18 will be not enough. pub static ref SLP_TOKEN_OWNERS: Mutex> = Mutex::new(Vec::with_capacity(18)); - pub static ref MM_CTX: MmArc = MmCtxBuilder::new().into_mm_arc(); + pub static ref MM_CTX: MmArc = MmCtxBuilder::new().with_conf(json!({"use_trading_proto_v2": true})).into_mm_arc(); + /// We need a second `MmCtx` instance when we use the same private keys for Maker and Taker across various tests. + /// When enabling coins for both Maker and Taker, two distinct coin instances are created. + /// This means that different instances of the same coin should have separate global nonce locks. + /// Utilizing different `MmCtx` instances allows us to assign Maker and Taker coins to separate `CoinsCtx`. + /// This approach addresses the `replacement transaction` issue, which occurs when different transactions share the same nonce. + pub static ref MM_CTX1: MmArc = MmCtxBuilder::new().with_conf(json!({"use_trading_proto_v2": true})).into_mm_arc(); pub static ref GETH_WEB3: Web3 = Web3::new(Http::new(GETH_RPC_URL).unwrap()); // Mutex used to prevent nonce re-usage during funding addresses used in tests pub static ref GETH_NONCE_LOCK: Mutex<()> = Mutex::new(()); } +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +lazy_static! { + pub static ref SEPOLIA_WEB3: Web3 = Web3::new(Http::new(SEPOLIA_RPC_URL).unwrap()); + pub static ref SEPOLIA_NONCE_LOCK: Mutex<()> = Mutex::new(()); + pub static ref SEPOLIA_TESTS_LOCK: Mutex<()> = Mutex::new(()); +} + pub static mut QICK_TOKEN_ADDRESS: Option = None; pub static mut QORTY_TOKEN_ADDRESS: Option = None; pub static mut QRC20_SWAP_CONTRACT_ADDRESS: Option = None; @@ -78,22 +87,45 @@ pub static mut QTUM_CONF_PATH: Option = None; pub static mut GETH_ACCOUNT: H160Eth = H160Eth::zero(); /// ERC20 token address on Geth dev node pub static mut GETH_ERC20_CONTRACT: H160Eth = H160Eth::zero(); +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub static mut SEPOLIA_ERC20_CONTRACT: H160Eth = H160Eth::zero(); /// Swap contract address on Geth dev node pub static mut GETH_SWAP_CONTRACT: H160Eth = H160Eth::zero(); +/// Maker Swap V2 contract address on Geth dev node +pub static mut GETH_MAKER_SWAP_V2: H160Eth = H160Eth::zero(); +/// Taker Swap V2 contract address on Geth dev node +pub static mut GETH_TAKER_SWAP_V2: H160Eth = H160Eth::zero(); +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub static mut SEPOLIA_TAKER_SWAP_V2: H160Eth = H160Eth::zero(); +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub static mut SEPOLIA_MAKER_SWAP_V2: H160Eth = H160Eth::zero(); /// Swap contract (with watchers support) address on Geth dev node pub static mut GETH_WATCHERS_SWAP_CONTRACT: H160Eth = H160Eth::zero(); /// ERC721 token address on Geth dev node pub static mut GETH_ERC721_CONTRACT: H160Eth = H160Eth::zero(); /// ERC1155 token address on Geth dev node pub static mut GETH_ERC1155_CONTRACT: H160Eth = H160Eth::zero(); -/// Nft Swap contract address on Geth dev node -pub static mut GETH_NFT_SWAP_CONTRACT: H160Eth = H160Eth::zero(); +/// NFT Maker Swap V2 contract address on Geth dev node +pub static mut GETH_NFT_MAKER_SWAP_V2: H160Eth = H160Eth::zero(); +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +/// NFT Maker Swap V2 contract address on Sepolia testnet +pub static mut SEPOLIA_ETOMIC_MAKER_NFT_SWAP_V2: H160Eth = H160Eth::zero(); pub static GETH_RPC_URL: &str = "http://127.0.0.1:8545"; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub static SEPOLIA_RPC_URL: &str = "https://ethereum-sepolia-rpc.publicnode.com"; pub const UTXO_ASSET_DOCKER_IMAGE: &str = "docker.io/artempikulin/testblockchain"; pub const UTXO_ASSET_DOCKER_IMAGE_WITH_TAG: &str = "docker.io/artempikulin/testblockchain:multiarch"; pub const GETH_DOCKER_IMAGE: &str = "docker.io/ethereum/client-go"; pub const GETH_DOCKER_IMAGE_WITH_TAG: &str = "docker.io/ethereum/client-go:stable"; +#[allow(dead_code)] +pub const SIA_DOCKER_IMAGE: &str = "docker.io/alrighttt/walletd-komodo"; +#[allow(dead_code)] +pub const SIA_DOCKER_IMAGE_WITH_TAG: &str = "docker.io/alrighttt/walletd-komodo:latest"; + +pub const NUCLEUS_IMAGE: &str = "docker.io/komodoofficial/nucleusd"; +pub const ATOM_IMAGE_WITH_TAG: &str = "docker.io/komodoofficial/gaiad:kdf-ci"; +pub const IBC_RELAYER_IMAGE_WITH_TAG: &str = "docker.io/komodoofficial/ibc-relayer:kdf-ci"; pub const QTUM_ADDRESS_LABEL: &str = "MM2_ADDRESS_LABEL"; @@ -107,12 +139,21 @@ pub const MYCOIN: &str = "MYCOIN"; /// Ticker of MYCOIN1 dockerized blockchain. pub const MYCOIN1: &str = "MYCOIN1"; -pub const ERC20_TOKEN_BYTES: &str = "6080604052600860ff16600a0a633b9aca000260005534801561002157600080fd5b50600054600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610c69806100776000396000f3006080604052600436106100a4576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100a9578063095ea7b31461013957806318160ddd1461019e57806323b872dd146101c9578063313ce5671461024e5780635a3b7e421461027f57806370a082311461030f57806395d89b4114610366578063a9059cbb146103f6578063dd62ed3e1461045b575b600080fd5b3480156100b557600080fd5b506100be6104d2565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100fe5780820151818401526020810190506100e3565b50505050905090810190601f16801561012b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561014557600080fd5b50610184600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061050b565b604051808215151515815260200191505060405180910390f35b3480156101aa57600080fd5b506101b36106bb565b6040518082815260200191505060405180910390f35b3480156101d557600080fd5b50610234600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506106c1565b604051808215151515815260200191505060405180910390f35b34801561025a57600080fd5b506102636109a1565b604051808260ff1660ff16815260200191505060405180910390f35b34801561028b57600080fd5b506102946109a6565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156102d45780820151818401526020810190506102b9565b50505050905090810190601f1680156103015780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561031b57600080fd5b50610350600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506109df565b6040518082815260200191505060405180910390f35b34801561037257600080fd5b5061037b6109f7565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103bb5780820151818401526020810190506103a0565b50505050905090810190601f1680156103e85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561040257600080fd5b50610441600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610a30565b604051808215151515815260200191505060405180910390f35b34801561046757600080fd5b506104bc600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610be1565b6040518082815260200191505060405180910390f35b6040805190810160405280600881526020017f515243205445535400000000000000000000000000000000000000000000000081525081565b60008260008173ffffffffffffffffffffffffffffffffffffffff161415151561053457600080fd5b60008314806105bf57506000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054145b15156105ca57600080fd5b82600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925856040518082815260200191505060405180910390a3600191505092915050565b60005481565b60008360008173ffffffffffffffffffffffffffffffffffffffff16141515156106ea57600080fd5b8360008173ffffffffffffffffffffffffffffffffffffffff161415151561071157600080fd5b610797600260008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c06565b600260008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610860600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c06565b600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506108ec600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c1f565b600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef866040518082815260200191505060405180910390a36001925050509392505050565b600881565b6040805190810160405280600981526020017f546f6b656e20302e31000000000000000000000000000000000000000000000081525081565b60016020528060005260406000206000915090505481565b6040805190810160405280600381526020017f515443000000000000000000000000000000000000000000000000000000000081525081565b60008260008173ffffffffffffffffffffffffffffffffffffffff1614151515610a5957600080fd5b610aa2600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205484610c06565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610b2e600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205484610c1f565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a3600191505092915050565b6002602052816000526040600020602052806000526040600020600091509150505481565b6000818310151515610c1457fe5b818303905092915050565b6000808284019050838110151515610c3357fe5b80915050929150505600a165627a7a723058207f2e5248b61b80365ea08a0f6d11ac0b47374c4dfd538de76bc2f19591bbbba40029"; -pub const SWAP_CONTRACT_BYTES: &str = "608060405234801561001057600080fd5b50611437806100206000396000f3fe60806040526004361061004a5760003560e01c806302ed292b1461004f5780630716326d146100de578063152cf3af1461017b57806346fc0294146101f65780639b415b2a14610294575b600080fd5b34801561005b57600080fd5b506100dc600480360360a081101561007257600080fd5b81019080803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610339565b005b3480156100ea57600080fd5b506101176004803603602081101561010157600080fd5b8101908080359060200190929190505050610867565b60405180846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526020018367ffffffffffffffff1667ffffffffffffffff16815260200182600381111561016557fe5b60ff168152602001935050505060405180910390f35b6101f46004803603608081101561019157600080fd5b8101908080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080356bffffffffffffffffffffffff19169060200190929190803567ffffffffffffffff1690602001909291905050506108bf565b005b34801561020257600080fd5b50610292600480360360a081101561021957600080fd5b81019080803590602001909291908035906020019092919080356bffffffffffffffffffffffff19169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610bd9565b005b610337600480360360c08110156102aa57600080fd5b810190808035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080356bffffffffffffffffffffffff19169060200190929190803567ffffffffffffffff169060200190929190505050610fe2565b005b6001600381111561034657fe5b600080878152602001908152602001600020600001601c9054906101000a900460ff16600381111561037457fe5b1461037e57600080fd5b6000600333836003600288604051602001808281526020019150506040516020818303038152906040526040518082805190602001908083835b602083106103db57805182526020820191506020810190506020830392506103b8565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa15801561041d573d6000803e3d6000fd5b5050506040513d602081101561043257600080fd5b8101908080519060200190929190505050604051602001808281526020019150506040516020818303038152906040526040518082805190602001908083835b602083106104955780518252602082019150602081019050602083039250610472565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa1580156104d7573d6000803e3d6000fd5b5050506040515160601b8689604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b602083106105fc57805182526020820191506020810190506020830392506105d9565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa15801561063e573d6000803e3d6000fd5b5050506040515160601b905060008087815260200190815260200160002060000160009054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461069657600080fd5b6002600080888152602001908152602001600020600001601c6101000a81548160ff021916908360038111156106c857fe5b0217905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561074e573373ffffffffffffffffffffffffffffffffffffffff166108fc869081150290604051600060405180830381858888f19350505050158015610748573d6000803e3d6000fd5b50610820565b60008390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156107da57600080fd5b505af11580156107ee573d6000803e3d6000fd5b505050506040513d602081101561080457600080fd5b810190808051906020019092919050505061081e57600080fd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e8685604051808381526020018281526020019250505060405180910390a1505050505050565b60006020528060005260406000206000915090508060000160009054906101000a900460601b908060000160149054906101000a900467ffffffffffffffff169080600001601c9054906101000a900460ff16905083565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141580156108fc5750600034115b801561094057506000600381111561091057fe5b600080868152602001908152602001600020600001601c9054906101000a900460ff16600381111561093e57fe5b145b61094957600080fd5b60006003843385600034604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b60208310610a6c5780518252602082019150602081019050602083039250610a49565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa158015610aae573d6000803e3d6000fd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff16815260200160016003811115610af757fe5b81525060008087815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c021790555060208201518160000160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550604082015181600001601c6101000a81548160ff02191690836003811115610b9357fe5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57856040518082815260200191505060405180910390a15050505050565b60016003811115610be657fe5b600080878152602001908152602001600020600001601c9054906101000a900460ff166003811115610c1457fe5b14610c1e57600080fd5b600060038233868689604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b60208310610d405780518252602082019150602081019050602083039250610d1d565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa158015610d82573d6000803e3d6000fd5b5050506040515160601b905060008087815260200190815260200160002060000160009054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916148015610e10575060008087815260200190815260200160002060000160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b610e1957600080fd5b6003600080888152602001908152602001600020600001601c6101000a81548160ff02191690836003811115610e4b57fe5b0217905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610ed1573373ffffffffffffffffffffffffffffffffffffffff166108fc869081150290604051600060405180830381858888f19350505050158015610ecb573d6000803e3d6000fd5b50610fa3565b60008390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015610f5d57600080fd5b505af1158015610f71573d6000803e3d6000fd5b505050506040513d6020811015610f8757600080fd5b8101908080519060200190929190505050610fa157600080fd5b505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba866040518082815260200191505060405180910390a1505050505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561101f5750600085115b801561106357506000600381111561103357fe5b600080888152602001908152602001600020600001601c9054906101000a900460ff16600381111561106157fe5b145b61106c57600080fd5b60006003843385888a604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b6020831061118e578051825260208201915060208101905060208303925061116b565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa1580156111d0573d6000803e3d6000fd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff1681526020016001600381111561121957fe5b81525060008089815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c021790555060208201518160000160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550604082015181600001601c6101000a81548160ff021916908360038111156112b557fe5b021790555090505060008590508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308a6040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019350505050602060405180830381600087803b15801561137d57600080fd5b505af1158015611391573d6000803e3d6000fd5b505050506040513d60208110156113a757600080fd5b81019080805190602001909291905050506113c157600080fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040518082815260200191505060405180910390a1505050505050505056fea265627a7a723158208c83db436905afce0b7be1012be64818c49323c12d451fe2ab6bce76ff6421c964736f6c63430005110032"; -pub const WATCHERS_SWAP_CONTRACT_BYTES: &str = "608060405234801561000f575f80fd5b50612aa48061001d5f395ff3fe608060405260043610610085575f3560e01c806346fc02941161005857806346fc0294146101275780636a3227861461014f5780639b415b2a1461016b578063b5985c4d14610193578063cd1dde34146101bb57610085565b806302ed292b146100895780630716326d146100b15780630971fd54146100ef578063152cf3af1461010b575b5f80fd5b348015610094575f80fd5b506100af60048036038101906100aa9190611e1d565b6101e3565b005b3480156100bc575f80fd5b506100d760048036038101906100d29190611e94565b610518565b6040516100e693929190611f8e565b60405180910390f35b6101096004803603810190610104919061206f565b610568565b005b6101256004803603810190610120919061210c565b610787565b005b348015610132575f80fd5b5061014d60048036038101906101489190612170565b61099d565b005b610169600480360381019061016491906121e7565b610c4d565b005b348015610176575f80fd5b50610191600480360381019061018c91906122ab565b610f61565b005b34801561019e575f80fd5b506101b960048036038101906101b49190612334565b611203565b005b3480156101c6575f80fd5b506101e160048036038101906101dc91906123f8565b611887565b005b600160038111156101f7576101f6611f1b565b5b5f808781526020019081526020015f205f01601c9054906101000a900460ff16600381111561022957610228611f1b565b5b14610232575f80fd5b5f60033383600360028860405160200161024c91906124dc565b6040516020818303038152906040526040516102689190612562565b602060405180830381855afa158015610283573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906102a6919061258c565b6040516020016102b691906124dc565b6040516020818303038152906040526040516102d29190612562565b602060405180830381855afa1580156102ed573d5f803e3d5ffd5b5050506040515160601b868960405160200161030d95949392919061263c565b6040516020818303038152906040526040516103299190612562565b602060405180830381855afa158015610344573d5f803e3d5ffd5b5050506040515160601b90505f808781526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610397575f80fd5b60025f808881526020019081526020015f205f01601c6101000a81548160ff021916908360038111156103cd576103cc611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361044e573373ffffffffffffffffffffffffffffffffffffffff166108fc8690811502906040515f60405180830381858888f19350505050158015610448573d5f803e3d5ffd5b506104d7565b5f8390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b815260040161048d9291906126b8565b6020604051808303815f875af11580156104a9573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104cd91906126f3565b6104d5575f80fd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e868560405161050892919061272d565b60405180910390a1505050505050565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900467ffffffffffffffff1690805f01601c9054906101000a900460ff16905083565b5f73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16141580156105a357505f34115b80156105f157505f60038111156105bd576105bc611f1b565b5b5f808981526020019081526020015f205f01601c9054906101000a900460ff1660038111156105ef576105ee611f1b565b5b145b6105f9575f80fd5b5f60038733885f3489898960405160200161061b9897969594939291906127e7565b6040516020818303038152906040526040516106379190612562565b602060405180830381855afa158015610652573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018667ffffffffffffffff168152602001600160038111156106a2576106a1611f1b565b5b8152505f808a81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561073e5761073d611f1b565b5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040516107759190612878565b60405180910390a15050505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141580156107c257505f34115b801561081057505f60038111156107dc576107db611f1b565b5b5f808681526020019081526020015f205f01601c9054906101000a900460ff16600381111561080e5761080d611f1b565b5b145b610818575f80fd5b5f60038433855f3460405160200161083495949392919061263c565b6040516020818303038152906040526040516108509190612562565b602060405180830381855afa15801561086b573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff168152602001600160038111156108bb576108ba611f1b565b5b8152505f808781526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561095757610956611f1b565b5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad578560405161098e9190612878565b60405180910390a15050505050565b600160038111156109b1576109b0611f1b565b5b5f808781526020019081526020015f205f01601c9054906101000a900460ff1660038111156109e3576109e2611f1b565b5b146109ec575f80fd5b5f60038233868689604051602001610a0895949392919061263c565b604051602081830303815290604052604051610a249190612562565b602060405180830381855afa158015610a3f573d5f803e3d5ffd5b5050506040515160601b90505f808781526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916148015610ac657505f808781526020019081526020015f205f0160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b610ace575f80fd5b60035f808881526020019081526020015f205f01601c6101000a81548160ff02191690836003811115610b0457610b03611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610b85573373ffffffffffffffffffffffffffffffffffffffff166108fc8690811502906040515f60405180830381858888f19350505050158015610b7f573d5f803e3d5ffd5b50610c0e565b5f8390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401610bc49291906126b8565b6020604051808303815f875af1158015610be0573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c0491906126f3565b610c0c575f80fd5b505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba86604051610c3d9190612878565b60405180910390a1505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614158015610c8857505f88115b8015610cd657505f6003811115610ca257610ca1611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff166003811115610cd457610cd3611f1b565b5b145b610cde575f80fd5b5f6003811115610cf157610cf0611f1b565b5b836003811115610d0457610d03611f1b565b5b14158015610d365750600380811115610d2057610d1f611f1b565b5b836003811115610d3357610d32611f1b565b5b14155b15610d4757803414610d46575f80fd5b5b5f60038733888b8d898989604051602001610d699897969594939291906127e7565b604051602081830303815290604052604051610d859190612562565b602060405180830381855afa158015610da0573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018667ffffffffffffffff16815260200160016003811115610df057610def611f1b565b5b8152505f808c81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff02191690836003811115610e8c57610e8b611f1b565b5b02179055509050505f8890508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308d6040518463ffffffff1660e01b8152600401610ed593929190612891565b6020604051808303815f875af1158015610ef1573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f1591906126f3565b610f1d575f80fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad578b604051610f4c9190612878565b60405180910390a15050505050505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614158015610f9c57505f85115b8015610fea57505f6003811115610fb657610fb5611f1b565b5b5f808881526020019081526020015f205f01601c9054906101000a900460ff166003811115610fe857610fe7611f1b565b5b145b610ff2575f80fd5b5f6003843385888a60405160200161100e95949392919061263c565b60405160208183030381529060405260405161102a9190612562565b602060405180830381855afa158015611045573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff1681526020016001600381111561109557611094611f1b565b5b8152505f808981526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561113157611130611f1b565b5b02179055509050505f8590508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308a6040518463ffffffff1660e01b815260040161117a93929190612891565b6020604051808303815f875af1158015611196573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111ba91906126f3565b6111c2575f80fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040516111f19190612878565b60405180910390a15050505050505050565b6001600381111561121757611216611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff16600381111561124957611248611f1b565b5b14611289576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161128090612920565b60405180910390fd5b5f60038587600360028c6040516020016112a391906124dc565b6040516020818303038152906040526040516112bf9190612562565b602060405180830381855afa1580156112da573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906112fd919061258c565b60405160200161130d91906124dc565b6040516020818303038152906040526040516113299190612562565b602060405180830381855afa158015611344573d5f803e3d5ffd5b5050506040515160601b8a8d89898960405160200161136a9897969594939291906127e7565b6040516020818303038152906040526040516113869190612562565b602060405180830381855afa1580156113a1573d5f803e3d5ffd5b5050506040515160601b90505f808b81526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461142b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161142290612988565b60405180910390fd5b60025f808c81526020019081526020015f205f01601c6101000a81548160ff0219169083600381111561146157611460611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff160361159e575f8060038111156114ad576114ac611f1b565b5b8560038111156114c0576114bf611f1b565b5b1480156114cb575083155b6114e057828a6114db91906129d3565b6114e2565b895b90508573ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015611527573d5f803e3d5ffd5b5060038081111561153b5761153a611f1b565b5b85600381111561154e5761154d611f1b565b5b03611598573373ffffffffffffffffffffffffffffffffffffffff166108fc8490811502906040515f60405180830381858888f19350505050158015611596573d5f803e3d5ffd5b505b50611786565b5f6003808111156115b2576115b1611f1b565b5b8560038111156115c5576115c4611f1b565b5b146115d057896115dd565b828a6115dc91906129d3565b5b90505f8890508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb88846040518363ffffffff1660e01b815260040161161e9291906126b8565b6020604051808303815f875af115801561163a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061165e91906126f3565b61169d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161169490612a50565b60405180910390fd5b6003808111156116b0576116af611f1b565b5b8660038111156116c3576116c2611f1b565b5b03611783578073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33866040518363ffffffff1660e01b81526004016117039291906126b8565b6020604051808303815f875af115801561171f573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061174391906126f3565b611782576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161177990612a50565b60405180910390fd5b5b50505b6002600381111561179a57611799611f1b565b5b8460038111156117ad576117ac611f1b565b5b036117f7578573ffffffffffffffffffffffffffffffffffffffff166108fc8390811502906040515f60405180830381858888f193505050501580156117f5573d5f803e3d5ffd5b505b8215611842573373ffffffffffffffffffffffffffffffffffffffff166108fc8390811502906040515f60405180830381858888f19350505050158015611840573d5f803e3d5ffd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e8a8960405161187392919061272d565b60405180910390a150505050505050505050565b6001600381111561189b5761189a611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff1660038111156118cd576118cc611f1b565b5b146118d6575f80fd5b5f600385878a8a8d8989896040516020016118f89897969594939291906127e7565b6040516020818303038152906040526040516119149190612562565b602060405180830381855afa15801561192f573d5f803e3d5ffd5b5050506040515160601b90505f808b81526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161480156119b657505f808b81526020019081526020015f205f0160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b6119be575f80fd5b60035f808c81526020019081526020015f205f01601c6101000a81548160ff021916908360038111156119f4576119f3611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1603611b27575f806003811115611a4057611a3f611f1b565b5b856003811115611a5357611a52611f1b565b5b14611a6957828a611a6491906129d3565b611a6b565b895b90508673ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015611ab0573d5f803e3d5ffd5b505f6003811115611ac457611ac3611f1b565b5b856003811115611ad757611ad6611f1b565b5b14611b21573373ffffffffffffffffffffffffffffffffffffffff166108fc8490811502906040515f60405180830381858888f19350505050158015611b1f573d5f803e3d5ffd5b505b50611d16565b5f600380811115611b3b57611b3a611f1b565b5b856003811115611b4e57611b4d611f1b565b5b14611b595789611b66565b828a611b6591906129d3565b5b90505f8890508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb89846040518363ffffffff1660e01b8152600401611ba79291906126b8565b6020604051808303815f875af1158015611bc3573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611be791906126f3565b611bef575f80fd5b600380811115611c0257611c01611f1b565b5b866003811115611c1557611c14611f1b565b5b03611ca2578073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33866040518363ffffffff1660e01b8152600401611c559291906126b8565b6020604051808303815f875af1158015611c71573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c9591906126f3565b611c9d575f80fd5b611d13565b5f6003811115611cb557611cb4611f1b565b5b866003811115611cc857611cc7611f1b565b5b14611d12573373ffffffffffffffffffffffffffffffffffffffff166108fc8590811502906040515f60405180830381858888f19350505050158015611d10573d5f803e3d5ffd5b505b5b50505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba8a604051611d459190612878565b60405180910390a150505050505050505050565b5f80fd5b5f819050919050565b611d6f81611d5d565b8114611d79575f80fd5b50565b5f81359050611d8a81611d66565b92915050565b5f819050919050565b611da281611d90565b8114611dac575f80fd5b50565b5f81359050611dbd81611d99565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f611dec82611dc3565b9050919050565b611dfc81611de2565b8114611e06575f80fd5b50565b5f81359050611e1781611df3565b92915050565b5f805f805f60a08688031215611e3657611e35611d59565b5b5f611e4388828901611d7c565b9550506020611e5488828901611daf565b9450506040611e6588828901611d7c565b9350506060611e7688828901611e09565b9250506080611e8788828901611e09565b9150509295509295909350565b5f60208284031215611ea957611ea8611d59565b5b5f611eb684828501611d7c565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b611ef381611ebf565b82525050565b5f67ffffffffffffffff82169050919050565b611f1581611ef9565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60048110611f5957611f58611f1b565b5b50565b5f819050611f6982611f48565b919050565b5f611f7882611f5c565b9050919050565b611f8881611f6e565b82525050565b5f606082019050611fa15f830186611eea565b611fae6020830185611f0c565b611fbb6040830184611f7f565b949350505050565b611fcc81611ebf565b8114611fd6575f80fd5b50565b5f81359050611fe781611fc3565b92915050565b611ff681611ef9565b8114612000575f80fd5b50565b5f8135905061201181611fed565b92915050565b60048110612023575f80fd5b50565b5f8135905061203481612017565b92915050565b5f8115159050919050565b61204e8161203a565b8114612058575f80fd5b50565b5f8135905061206981612045565b92915050565b5f805f805f805f60e0888a03121561208a57612089611d59565b5b5f6120978a828b01611d7c565b97505060206120a88a828b01611e09565b96505060406120b98a828b01611fd9565b95505060606120ca8a828b01612003565b94505060806120db8a828b01612026565b93505060a06120ec8a828b0161205b565b92505060c06120fd8a828b01611daf565b91505092959891949750929550565b5f805f806080858703121561212457612123611d59565b5b5f61213187828801611d7c565b945050602061214287828801611e09565b935050604061215387828801611fd9565b925050606061216487828801612003565b91505092959194509250565b5f805f805f60a0868803121561218957612188611d59565b5b5f61219688828901611d7c565b95505060206121a788828901611daf565b94505060406121b888828901611fd9565b93505060606121c988828901611e09565b92505060806121da88828901611e09565b9150509295509295909350565b5f805f805f805f805f6101208a8c03121561220557612204611d59565b5b5f6122128c828d01611d7c565b99505060206122238c828d01611daf565b98505060406122348c828d01611e09565b97505060606122458c828d01611e09565b96505060806122568c828d01611fd9565b95505060a06122678c828d01612003565b94505060c06122788c828d01612026565b93505060e06122898c828d0161205b565b92505061010061229b8c828d01611daf565b9150509295985092959850929598565b5f805f805f8060c087890312156122c5576122c4611d59565b5b5f6122d289828a01611d7c565b96505060206122e389828a01611daf565b95505060406122f489828a01611e09565b945050606061230589828a01611e09565b935050608061231689828a01611fd9565b92505060a061232789828a01612003565b9150509295509295509295565b5f805f805f805f805f6101208a8c03121561235257612351611d59565b5b5f61235f8c828d01611d7c565b99505060206123708c828d01611daf565b98505060406123818c828d01611d7c565b97505060606123928c828d01611e09565b96505060806123a38c828d01611e09565b95505060a06123b48c828d01611e09565b94505060c06123c58c828d01612026565b93505060e06123d68c828d0161205b565b9250506101006123e88c828d01611daf565b9150509295985092959850929598565b5f805f805f805f805f6101208a8c03121561241657612415611d59565b5b5f6124238c828d01611d7c565b99505060206124348c828d01611daf565b98505060406124458c828d01611fd9565b97505060606124568c828d01611e09565b96505060806124678c828d01611e09565b95505060a06124788c828d01611e09565b94505060c06124898c828d01612026565b93505060e061249a8c828d0161205b565b9250506101006124ac8c828d01611daf565b9150509295985092959850929598565b5f819050919050565b6124d66124d182611d5d565b6124bc565b82525050565b5f6124e782846124c5565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b5f5b8381101561252757808201518184015260208101905061250c565b5f8484015250505050565b5f61253c826124f6565b6125468185612500565b935061255681856020860161250a565b80840191505092915050565b5f61256d8284612532565b915081905092915050565b5f8151905061258681611d66565b92915050565b5f602082840312156125a1576125a0611d59565b5b5f6125ae84828501612578565b91505092915050565b5f8160601b9050919050565b5f6125cd826125b7565b9050919050565b5f6125de826125c3565b9050919050565b6125f66125f182611de2565b6125d4565b82525050565b5f819050919050565b61261661261182611ebf565b6125fc565b82525050565b5f819050919050565b61263661263182611d90565b61261c565b82525050565b5f61264782886125e5565b60148201915061265782876125e5565b6014820191506126678286612605565b60148201915061267782856125e5565b6014820191506126878284612625565b6020820191508190509695505050505050565b6126a381611de2565b82525050565b6126b281611d90565b82525050565b5f6040820190506126cb5f83018561269a565b6126d860208301846126a9565b9392505050565b5f815190506126ed81612045565b92915050565b5f6020828403121561270857612707611d59565b5b5f612715848285016126df565b91505092915050565b61272781611d5d565b82525050565b5f6040820190506127405f83018561271e565b61274d602083018461271e565b9392505050565b6004811061276557612764611f1b565b5b50565b5f81905061277582612754565b919050565b5f61278482612768565b9050919050565b5f8160f81b9050919050565b5f6127a18261278b565b9050919050565b6127b96127b48261277a565b612797565b82525050565b5f6127c982612797565b9050919050565b6127e16127dc8261203a565b6127bf565b82525050565b5f6127f2828b6125e5565b601482019150612802828a6125e5565b6014820191506128128289612605565b60148201915061282282886125e5565b6014820191506128328287612625565b60208201915061284282866127a8565b60018201915061285282856127d0565b6001820191506128628284612625565b6020820191508190509998505050505050505050565b5f60208201905061288b5f83018461271e565b92915050565b5f6060820190506128a45f83018661269a565b6128b1602083018561269a565b6128be60408301846126a9565b949350505050565b5f82825260208201905092915050565b7f5061796d656e7420776173206e6f742073656e740000000000000000000000005f82015250565b5f61290a6014836128c6565b9150612915826128d6565b602082019050919050565b5f6020820190508181035f830152612937816128fe565b9050919050565b7f496e76616c6964207061796d656e7420686173680000000000000000000000005f82015250565b5f6129726014836128c6565b915061297d8261293e565b602082019050919050565b5f6020820190508181035f83015261299f81612966565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6129dd82611d90565b91506129e883611d90565b9250828203905081811115612a00576129ff6129a6565b5b92915050565b7f546f6b656e207472616e73666572206661696c656400000000000000000000005f82015250565b5f612a3a6015836128c6565b9150612a4582612a06565b602082019050919050565b5f6020820190508181035f830152612a6781612a2e565b905091905056fea26469706673582212203106867e1b147b377237cde0aba42d82faf0282b83d7b6d62cca039d0b7f840564736f6c63430008160033"; -pub const ERC721_TEST_TOKEN_BYTES: &str = "608060405234801562000010575f80fd5b50604051620022ac380380620022ac8339818101604052810190620000369190620001ea565b8181815f9081620000489190620004a4565b5080600190816200005a9190620004a4565b505050505062000588565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b620000c6826200007e565b810181811067ffffffffffffffff82111715620000e857620000e76200008e565b5b80604052505050565b5f620000fc62000065565b90506200010a8282620000bb565b919050565b5f67ffffffffffffffff8211156200012c576200012b6200008e565b5b62000137826200007e565b9050602081019050919050565b5f5b838110156200016357808201518184015260208101905062000146565b5f8484015250505050565b5f620001846200017e846200010f565b620000f1565b905082815260208101848484011115620001a357620001a26200007a565b5b620001b084828562000144565b509392505050565b5f82601f830112620001cf57620001ce62000076565b5b8151620001e18482602086016200016e565b91505092915050565b5f80604083850312156200020357620002026200006e565b5b5f83015167ffffffffffffffff81111562000223576200022262000072565b5b6200023185828601620001b8565b925050602083015167ffffffffffffffff81111562000255576200025462000072565b5b6200026385828601620001b8565b9150509250929050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680620002bc57607f821691505b602082108103620002d257620002d162000277565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620003367fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002f9565b620003428683620002f9565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f6200038c6200038662000380846200035a565b62000363565b6200035a565b9050919050565b5f819050919050565b620003a7836200036c565b620003bf620003b68262000393565b84845462000305565b825550505050565b5f90565b620003d5620003c7565b620003e28184846200039c565b505050565b5b818110156200040957620003fd5f82620003cb565b600181019050620003e8565b5050565b601f82111562000458576200042281620002d8565b6200042d84620002ea565b810160208510156200043d578190505b620004556200044c85620002ea565b830182620003e7565b50505b505050565b5f82821c905092915050565b5f6200047a5f19846008026200045d565b1980831691505092915050565b5f62000494838362000469565b9150826002028217905092915050565b620004af826200026d565b67ffffffffffffffff811115620004cb57620004ca6200008e565b5b620004d78254620002a4565b620004e48282856200040d565b5f60209050601f8311600181146200051a575f841562000505578287015190505b62000511858262000487565b86555062000580565b601f1984166200052a86620002d8565b5f5b8281101562000553578489015182556001820191506020850194506020810190506200052c565b868310156200057357848901516200056f601f89168262000469565b8355505b6001600288020188555050505b505050505050565b611d1680620005965f395ff3fe608060405234801561000f575f80fd5b50600436106100e8575f3560e01c80636352211e1161008a578063a22cb46511610064578063a22cb46514610258578063b88d4fde14610274578063c87b56dd14610290578063e985e9c5146102c0576100e8565b80636352211e146101da57806370a082311461020a57806395d89b411461023a576100e8565b8063095ea7b3116100c6578063095ea7b31461016a57806323b872dd1461018657806340c10f19146101a257806342842e0e146101be576100e8565b806301ffc9a7146100ec57806306fdde031461011c578063081812fc1461013a575b5f80fd5b610106600480360381019061010191906115a7565b6102f0565b60405161011391906115ec565b60405180910390f35b6101246103d1565b604051610131919061168f565b60405180910390f35b610154600480360381019061014f91906116e2565b610460565b604051610161919061174c565b60405180910390f35b610184600480360381019061017f919061178f565b61047b565b005b6101a0600480360381019061019b91906117cd565b610491565b005b6101bc60048036038101906101b7919061178f565b610590565b005b6101d860048036038101906101d391906117cd565b61059e565b005b6101f460048036038101906101ef91906116e2565b6105bd565b604051610201919061174c565b60405180910390f35b610224600480360381019061021f919061181d565b6105ce565b6040516102319190611857565b60405180910390f35b610242610684565b60405161024f919061168f565b60405180910390f35b610272600480360381019061026d919061189a565b610714565b005b61028e60048036038101906102899190611a04565b61072a565b005b6102aa60048036038101906102a591906116e2565b610747565b6040516102b7919061168f565b60405180910390f35b6102da60048036038101906102d59190611a84565b6107ad565b6040516102e791906115ec565b60405180910390f35b5f7f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806103ba57507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103ca57506103c98261083b565b5b9050919050565b60605f80546103df90611aef565b80601f016020809104026020016040519081016040528092919081815260200182805461040b90611aef565b80156104565780601f1061042d57610100808354040283529160200191610456565b820191905f5260205f20905b81548152906001019060200180831161043957829003601f168201915b5050505050905090565b5f61046a826108a4565b506104748261092a565b9050919050565b61048d8282610488610963565b61096a565b5050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610501575f6040517f64a0ae920000000000000000000000000000000000000000000000000000000081526004016104f8919061174c565b60405180910390fd5b5f610514838361050f610963565b61097c565b90508373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161461058a578382826040517f64283d7b00000000000000000000000000000000000000000000000000000000815260040161058193929190611b1f565b60405180910390fd5b50505050565b61059a8282610b87565b5050565b6105b883838360405180602001604052805f81525061072a565b505050565b5f6105c7826108a4565b9050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361063f575f6040517f89c62b64000000000000000000000000000000000000000000000000000000008152600401610636919061174c565b60405180910390fd5b60035f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b60606001805461069390611aef565b80601f01602080910402602001604051908101604052809291908181526020018280546106bf90611aef565b801561070a5780601f106106e15761010080835404028352916020019161070a565b820191905f5260205f20905b8154815290600101906020018083116106ed57829003601f168201915b5050505050905090565b61072661071f610963565b8383610c7a565b5050565b610735848484610491565b61074184848484610de3565b50505050565b6060610752826108a4565b505f61075c610f95565b90505f81511161077a5760405180602001604052805f8152506107a5565b8061078484610fab565b604051602001610795929190611b8e565b6040516020818303038152906040525b915050919050565b5f60055f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f806108af83611075565b90505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092157826040517f7e2732890000000000000000000000000000000000000000000000000000000081526004016109189190611857565b60405180910390fd5b80915050919050565b5f60045f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b5f33905090565b61097783838360016110ae565b505050565b5f8061098784611075565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16146109c8576109c781848661126d565b5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610a5357610a075f855f806110ae565b600160035f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825403925050819055505b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614610ad257600160035f8773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8460025f8681526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550838573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4809150509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610bf7575f6040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610bee919061174c565b60405180910390fd5b5f610c0383835f61097c565b90505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610c75575f6040517f73c6ac6e000000000000000000000000000000000000000000000000000000008152600401610c6c919061174c565b60405180910390fd5b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610cea57816040517f5b08ba18000000000000000000000000000000000000000000000000000000008152600401610ce1919061174c565b60405180910390fd5b8060055f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610dd691906115ec565b60405180910390a3505050565b5f8373ffffffffffffffffffffffffffffffffffffffff163b1115610f8f578273ffffffffffffffffffffffffffffffffffffffff1663150b7a02610e26610963565b8685856040518563ffffffff1660e01b8152600401610e489493929190611c03565b6020604051808303815f875af1925050508015610e8357506040513d601f19601f82011682018060405250810190610e809190611c61565b60015b610f04573d805f8114610eb1576040519150601f19603f3d011682016040523d82523d5f602084013e610eb6565b606091505b505f815103610efc57836040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610ef3919061174c565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614610f8d57836040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610f84919061174c565b60405180910390fd5b505b50505050565b606060405180602001604052805f815250905090565b60605f6001610fb984611330565b0190505f8167ffffffffffffffff811115610fd757610fd66118e0565b5b6040519080825280601f01601f1916602001820160405280156110095781602001600182028036833780820191505090505b5090505f82602001820190505b60011561106a578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a858161105f5761105e611c8c565b5b0494505f8503611016575b819350505050919050565b5f60025f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b80806110e657505f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b15611218575f6110f5846108a4565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561115f57508273ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b8015611172575061117081846107ad565b155b156111b457826040517fa9fbf51f0000000000000000000000000000000000000000000000000000000081526004016111ab919061174c565b60405180910390fd5b811561121657838573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45b505b8360045f8581526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050565b611278838383611481565b61132b575f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036112ec57806040517f7e2732890000000000000000000000000000000000000000000000000000000081526004016112e39190611857565b60405180910390fd5b81816040517f177e802f000000000000000000000000000000000000000000000000000000008152600401611322929190611cb9565b60405180910390fd5b505050565b5f805f90507a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000831061138c577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161138257611381611c8c565b5b0492506040810190505b6d04ee2d6d415b85acef810000000083106113c9576d04ee2d6d415b85acef810000000083816113bf576113be611c8c565b5b0492506020810190505b662386f26fc1000083106113f857662386f26fc1000083816113ee576113ed611c8c565b5b0492506010810190505b6305f5e1008310611421576305f5e100838161141757611416611c8c565b5b0492506008810190505b612710831061144657612710838161143c5761143b611c8c565b5b0492506004810190505b60648310611469576064838161145f5761145e611c8c565b5b0492506002810190505b600a8310611478576001810190505b80915050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561153857508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614806114f957506114f884846107ad565b5b8061153757508273ffffffffffffffffffffffffffffffffffffffff1661151f8361092a565b73ffffffffffffffffffffffffffffffffffffffff16145b5b90509392505050565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61158681611552565b8114611590575f80fd5b50565b5f813590506115a18161157d565b92915050565b5f602082840312156115bc576115bb61154a565b5b5f6115c984828501611593565b91505092915050565b5f8115159050919050565b6115e6816115d2565b82525050565b5f6020820190506115ff5f8301846115dd565b92915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b8381101561163c578082015181840152602081019050611621565b5f8484015250505050565b5f601f19601f8301169050919050565b5f61166182611605565b61166b818561160f565b935061167b81856020860161161f565b61168481611647565b840191505092915050565b5f6020820190508181035f8301526116a78184611657565b905092915050565b5f819050919050565b6116c1816116af565b81146116cb575f80fd5b50565b5f813590506116dc816116b8565b92915050565b5f602082840312156116f7576116f661154a565b5b5f611704848285016116ce565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6117368261170d565b9050919050565b6117468161172c565b82525050565b5f60208201905061175f5f83018461173d565b92915050565b61176e8161172c565b8114611778575f80fd5b50565b5f8135905061178981611765565b92915050565b5f80604083850312156117a5576117a461154a565b5b5f6117b28582860161177b565b92505060206117c3858286016116ce565b9150509250929050565b5f805f606084860312156117e4576117e361154a565b5b5f6117f18682870161177b565b93505060206118028682870161177b565b9250506040611813868287016116ce565b9150509250925092565b5f602082840312156118325761183161154a565b5b5f61183f8482850161177b565b91505092915050565b611851816116af565b82525050565b5f60208201905061186a5f830184611848565b92915050565b611879816115d2565b8114611883575f80fd5b50565b5f8135905061189481611870565b92915050565b5f80604083850312156118b0576118af61154a565b5b5f6118bd8582860161177b565b92505060206118ce85828601611886565b9150509250929050565b5f80fd5b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61191682611647565b810181811067ffffffffffffffff82111715611935576119346118e0565b5b80604052505050565b5f611947611541565b9050611953828261190d565b919050565b5f67ffffffffffffffff821115611972576119716118e0565b5b61197b82611647565b9050602081019050919050565b828183375f83830152505050565b5f6119a86119a384611958565b61193e565b9050828152602081018484840111156119c4576119c36118dc565b5b6119cf848285611988565b509392505050565b5f82601f8301126119eb576119ea6118d8565b5b81356119fb848260208601611996565b91505092915050565b5f805f8060808587031215611a1c57611a1b61154a565b5b5f611a298782880161177b565b9450506020611a3a8782880161177b565b9350506040611a4b878288016116ce565b925050606085013567ffffffffffffffff811115611a6c57611a6b61154e565b5b611a78878288016119d7565b91505092959194509250565b5f8060408385031215611a9a57611a9961154a565b5b5f611aa78582860161177b565b9250506020611ab88582860161177b565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680611b0657607f821691505b602082108103611b1957611b18611ac2565b5b50919050565b5f606082019050611b325f83018661173d565b611b3f6020830185611848565b611b4c604083018461173d565b949350505050565b5f81905092915050565b5f611b6882611605565b611b728185611b54565b9350611b8281856020860161161f565b80840191505092915050565b5f611b998285611b5e565b9150611ba58284611b5e565b91508190509392505050565b5f81519050919050565b5f82825260208201905092915050565b5f611bd582611bb1565b611bdf8185611bbb565b9350611bef81856020860161161f565b611bf881611647565b840191505092915050565b5f608082019050611c165f83018761173d565b611c23602083018661173d565b611c306040830185611848565b8181036060830152611c428184611bcb565b905095945050505050565b5f81519050611c5b8161157d565b92915050565b5f60208284031215611c7657611c7561154a565b5b5f611c8384828501611c4d565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f604082019050611ccc5f83018561173d565b611cd96020830184611848565b939250505056fea26469706673582212207439b47c2a9a1624955997732075917bbf1da26949d000c778f561eb5687576164736f6c63430008180033"; -pub const ERC1155_TEST_TOKEN_BYTES: &str = "608060405234801562000010575f80fd5b50604051620024eb380380620024eb8339818101604052810190620000369190620001ea565b8062000048816200005060201b60201c565b505062000554565b806002908162000061919062000470565b5050565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b620000c6826200007e565b810181811067ffffffffffffffff82111715620000e857620000e76200008e565b5b80604052505050565b5f620000fc62000065565b90506200010a8282620000bb565b919050565b5f67ffffffffffffffff8211156200012c576200012b6200008e565b5b62000137826200007e565b9050602081019050919050565b5f5b838110156200016357808201518184015260208101905062000146565b5f8484015250505050565b5f620001846200017e846200010f565b620000f1565b905082815260208101848484011115620001a357620001a26200007a565b5b620001b084828562000144565b509392505050565b5f82601f830112620001cf57620001ce62000076565b5b8151620001e18482602086016200016e565b91505092915050565b5f602082840312156200020257620002016200006e565b5b5f82015167ffffffffffffffff81111562000222576200022162000072565b5b6200023084828501620001b8565b91505092915050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200028857607f821691505b6020821081036200029e576200029d62000243565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620003027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002c5565b6200030e8683620002c5565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f62000358620003526200034c8462000326565b6200032f565b62000326565b9050919050565b5f819050919050565b620003738362000338565b6200038b62000382826200035f565b848454620002d1565b825550505050565b5f90565b620003a162000393565b620003ae81848462000368565b505050565b5b81811015620003d557620003c95f8262000397565b600181019050620003b4565b5050565b601f8211156200042457620003ee81620002a4565b620003f984620002b6565b8101602085101562000409578190505b620004216200041885620002b6565b830182620003b3565b50505b505050565b5f82821c905092915050565b5f620004465f198460080262000429565b1980831691505092915050565b5f62000460838362000435565b9150826002028217905092915050565b6200047b8262000239565b67ffffffffffffffff8111156200049757620004966200008e565b5b620004a3825462000270565b620004b0828285620003d9565b5f60209050601f831160018114620004e6575f8415620004d1578287015190505b620004dd858262000453565b8655506200054c565b601f198416620004f686620002a4565b5f5b828110156200051f57848901518255600182019150602085019450602081019050620004f8565b868310156200053f57848901516200053b601f89168262000435565b8355505b6001600288020188555050505b505050505050565b611f8980620005625f395ff3fe608060405234801561000f575f80fd5b5060043610610090575f3560e01c80634e1273f4116100645780634e1273f414610140578063731133e914610170578063a22cb4651461018c578063e985e9c5146101a8578063f242432a146101d857610090565b8062fdd58e1461009457806301ffc9a7146100c45780630e89341c146100f45780632eb2c2d614610124575b5f80fd5b6100ae60048036038101906100a991906113bd565b6101f4565b6040516100bb919061140a565b60405180910390f35b6100de60048036038101906100d99190611478565b610249565b6040516100eb91906114bd565b60405180910390f35b61010e600480360381019061010991906114d6565b61032a565b60405161011b919061158b565b60405180910390f35b61013e6004803603810190610139919061179b565b6103bc565b005b61015a60048036038101906101559190611926565b610463565b6040516101679190611a53565b60405180910390f35b61018a60048036038101906101859190611a73565b61056a565b005b6101a660048036038101906101a19190611b1d565b61057c565b005b6101c260048036038101906101bd9190611b5b565b610592565b6040516101cf91906114bd565b60405180910390f35b6101f260048036038101906101ed9190611b99565b610620565b005b5f805f8381526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f7fd9b67a26000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061031357507f0e89341c000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103235750610322826106c7565b5b9050919050565b60606002805461033990611c59565b80601f016020809104026020016040519081016040528092919081815260200182805461036590611c59565b80156103b05780601f10610387576101008083540402835291602001916103b0565b820191905f5260205f20905b81548152906001019060200180831161039357829003601f168201915b50505050509050919050565b5f6103c5610730565b90508073ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161415801561040a57506104088682610592565b155b1561044e5780866040517fe237d922000000000000000000000000000000000000000000000000000000008152600401610445929190611c98565b60405180910390fd5b61045b8686868686610737565b505050505050565b606081518351146104af57815183516040517f5b0599910000000000000000000000000000000000000000000000000000000081526004016104a6929190611cbf565b60405180910390fd5b5f835167ffffffffffffffff8111156104cb576104ca6115af565b5b6040519080825280602002602001820160405280156104f95781602001602082028036833780820191505090505b5090505f5b845181101561055f5761053561051d828761082b90919063ffffffff16565b610530838761083e90919063ffffffff16565b6101f4565b82828151811061054857610547611ce6565b5b6020026020010181815250508060010190506104fe565b508091505092915050565b61057684848484610851565b50505050565b61058e610587610730565b83836108e6565b5050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f610629610730565b90508073ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161415801561066e575061066c8682610592565b155b156106b25780866040517fe237d9220000000000000000000000000000000000000000000000000000000081526004016106a9929190611c98565b60405180910390fd5b6106bf8686868686610a4f565b505050505050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f33905090565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036107a7575f6040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161079e9190611d13565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603610817575f6040517f01a8351400000000000000000000000000000000000000000000000000000000815260040161080e9190611d13565b60405180910390fd5b6108248585858585610b55565b5050505050565b5f60208202602084010151905092915050565b5f60208202602084010151905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036108c1575f6040517f57f447ce0000000000000000000000000000000000000000000000000000000081526004016108b89190611d13565b60405180910390fd5b5f806108cd8585610c01565b915091506108de5f87848487610b55565b505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610956575f6040517fced3e10000000000000000000000000000000000000000000000000000000000815260040161094d9190611d13565b60405180910390fd5b8060015f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610a4291906114bd565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610abf575f6040517f57f447ce000000000000000000000000000000000000000000000000000000008152600401610ab69190611d13565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603610b2f575f6040517f01a83514000000000000000000000000000000000000000000000000000000008152600401610b269190611d13565b60405180910390fd5b5f80610b3b8585610c01565b91509150610b4c8787848487610b55565b50505050505050565b610b6185858585610c31565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614610bfa575f610b9d610730565b90506001845103610be9575f610bbc5f8661083e90919063ffffffff16565b90505f610bd25f8661083e90919063ffffffff16565b9050610be2838989858589610fc1565b5050610bf8565b610bf7818787878787611170565b5b505b5050505050565b60608060405191506001825283602083015260408201905060018152826020820152604081016040529250929050565b8051825114610c7b57815181516040517f5b059991000000000000000000000000000000000000000000000000000000008152600401610c72929190611cbf565b60405180910390fd5b5f610c84610730565b90505f5b8351811015610e80575f610ca5828661083e90919063ffffffff16565b90505f610cbb838661083e90919063ffffffff16565b90505f73ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614610dde575f805f8481526020019081526020015f205f8a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905081811015610d8a57888183856040517f03dee4c5000000000000000000000000000000000000000000000000000000008152600401610d819493929190611d2c565b60405180910390fd5b8181035f808581526020019081526020015f205f8b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1614610e7357805f808481526020019081526020015f205f8973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f828254610e6b9190611d9c565b925050819055505b5050806001019050610c88565b506001835103610f3b575f610e9e5f8561083e90919063ffffffff16565b90505f610eb45f8561083e90919063ffffffff16565b90508573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f628585604051610f2c929190611cbf565b60405180910390a45050610fba565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8686604051610fb1929190611dcf565b60405180910390a45b5050505050565b5f8473ffffffffffffffffffffffffffffffffffffffff163b1115611168578373ffffffffffffffffffffffffffffffffffffffff1663f23a6e6187878686866040518663ffffffff1660e01b8152600401611021959493929190611e56565b6020604051808303815f875af192505050801561105c57506040513d601f19601f820116820180604052508101906110599190611ec2565b60015b6110dd573d805f811461108a576040519150601f19603f3d011682016040523d82523d5f602084013e61108f565b606091505b505f8151036110d557846040517f57f447ce0000000000000000000000000000000000000000000000000000000081526004016110cc9190611d13565b60405180910390fd5b805181602001fd5b63f23a6e6160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461116657846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161115d9190611d13565b60405180910390fd5b505b505050505050565b5f8473ffffffffffffffffffffffffffffffffffffffff163b1115611317578373ffffffffffffffffffffffffffffffffffffffff1663bc197c8187878686866040518663ffffffff1660e01b81526004016111d0959493929190611eed565b6020604051808303815f875af192505050801561120b57506040513d601f19601f820116820180604052508101906112089190611ec2565b60015b61128c573d805f8114611239576040519150601f19603f3d011682016040523d82523d5f602084013e61123e565b606091505b505f81510361128457846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161127b9190611d13565b60405180910390fd5b805181602001fd5b63bc197c8160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461131557846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161130c9190611d13565b60405180910390fd5b505b505050505050565b5f604051905090565b5f80fd5b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61135982611330565b9050919050565b6113698161134f565b8114611373575f80fd5b50565b5f8135905061138481611360565b92915050565b5f819050919050565b61139c8161138a565b81146113a6575f80fd5b50565b5f813590506113b781611393565b92915050565b5f80604083850312156113d3576113d2611328565b5b5f6113e085828601611376565b92505060206113f1858286016113a9565b9150509250929050565b6114048161138a565b82525050565b5f60208201905061141d5f8301846113fb565b92915050565b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61145781611423565b8114611461575f80fd5b50565b5f813590506114728161144e565b92915050565b5f6020828403121561148d5761148c611328565b5b5f61149a84828501611464565b91505092915050565b5f8115159050919050565b6114b7816114a3565b82525050565b5f6020820190506114d05f8301846114ae565b92915050565b5f602082840312156114eb576114ea611328565b5b5f6114f8848285016113a9565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b8381101561153857808201518184015260208101905061151d565b5f8484015250505050565b5f601f19601f8301169050919050565b5f61155d82611501565b611567818561150b565b935061157781856020860161151b565b61158081611543565b840191505092915050565b5f6020820190508181035f8301526115a38184611553565b905092915050565b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6115e582611543565b810181811067ffffffffffffffff82111715611604576116036115af565b5b80604052505050565b5f61161661131f565b905061162282826115dc565b919050565b5f67ffffffffffffffff821115611641576116406115af565b5b602082029050602081019050919050565b5f80fd5b5f61166861166384611627565b61160d565b9050808382526020820190506020840283018581111561168b5761168a611652565b5b835b818110156116b457806116a088826113a9565b84526020840193505060208101905061168d565b5050509392505050565b5f82601f8301126116d2576116d16115ab565b5b81356116e2848260208601611656565b91505092915050565b5f80fd5b5f67ffffffffffffffff821115611709576117086115af565b5b61171282611543565b9050602081019050919050565b828183375f83830152505050565b5f61173f61173a846116ef565b61160d565b90508281526020810184848401111561175b5761175a6116eb565b5b61176684828561171f565b509392505050565b5f82601f830112611782576117816115ab565b5b813561179284826020860161172d565b91505092915050565b5f805f805f60a086880312156117b4576117b3611328565b5b5f6117c188828901611376565b95505060206117d288828901611376565b945050604086013567ffffffffffffffff8111156117f3576117f261132c565b5b6117ff888289016116be565b935050606086013567ffffffffffffffff8111156118205761181f61132c565b5b61182c888289016116be565b925050608086013567ffffffffffffffff81111561184d5761184c61132c565b5b6118598882890161176e565b9150509295509295909350565b5f67ffffffffffffffff8211156118805761187f6115af565b5b602082029050602081019050919050565b5f6118a361189e84611866565b61160d565b905080838252602082019050602084028301858111156118c6576118c5611652565b5b835b818110156118ef57806118db8882611376565b8452602084019350506020810190506118c8565b5050509392505050565b5f82601f83011261190d5761190c6115ab565b5b813561191d848260208601611891565b91505092915050565b5f806040838503121561193c5761193b611328565b5b5f83013567ffffffffffffffff8111156119595761195861132c565b5b611965858286016118f9565b925050602083013567ffffffffffffffff8111156119865761198561132c565b5b611992858286016116be565b9150509250929050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b6119ce8161138a565b82525050565b5f6119df83836119c5565b60208301905092915050565b5f602082019050919050565b5f611a018261199c565b611a0b81856119a6565b9350611a16836119b6565b805f5b83811015611a46578151611a2d88826119d4565b9750611a38836119eb565b925050600181019050611a19565b5085935050505092915050565b5f6020820190508181035f830152611a6b81846119f7565b905092915050565b5f805f8060808587031215611a8b57611a8a611328565b5b5f611a9887828801611376565b9450506020611aa9878288016113a9565b9350506040611aba878288016113a9565b925050606085013567ffffffffffffffff811115611adb57611ada61132c565b5b611ae78782880161176e565b91505092959194509250565b611afc816114a3565b8114611b06575f80fd5b50565b5f81359050611b1781611af3565b92915050565b5f8060408385031215611b3357611b32611328565b5b5f611b4085828601611376565b9250506020611b5185828601611b09565b9150509250929050565b5f8060408385031215611b7157611b70611328565b5b5f611b7e85828601611376565b9250506020611b8f85828601611376565b9150509250929050565b5f805f805f60a08688031215611bb257611bb1611328565b5b5f611bbf88828901611376565b9550506020611bd088828901611376565b9450506040611be1888289016113a9565b9350506060611bf2888289016113a9565b925050608086013567ffffffffffffffff811115611c1357611c1261132c565b5b611c1f8882890161176e565b9150509295509295909350565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680611c7057607f821691505b602082108103611c8357611c82611c2c565b5b50919050565b611c928161134f565b82525050565b5f604082019050611cab5f830185611c89565b611cb86020830184611c89565b9392505050565b5f604082019050611cd25f8301856113fb565b611cdf60208301846113fb565b9392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f602082019050611d265f830184611c89565b92915050565b5f608082019050611d3f5f830187611c89565b611d4c60208301866113fb565b611d5960408301856113fb565b611d6660608301846113fb565b95945050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f611da68261138a565b9150611db18361138a565b9250828201905080821115611dc957611dc8611d6f565b5b92915050565b5f6040820190508181035f830152611de781856119f7565b90508181036020830152611dfb81846119f7565b90509392505050565b5f81519050919050565b5f82825260208201905092915050565b5f611e2882611e04565b611e328185611e0e565b9350611e4281856020860161151b565b611e4b81611543565b840191505092915050565b5f60a082019050611e695f830188611c89565b611e766020830187611c89565b611e8360408301866113fb565b611e9060608301856113fb565b8181036080830152611ea28184611e1e565b90509695505050505050565b5f81519050611ebc8161144e565b92915050565b5f60208284031215611ed757611ed6611328565b5b5f611ee484828501611eae565b91505092915050565b5f60a082019050611f005f830188611c89565b611f0d6020830187611c89565b8181036040830152611f1f81866119f7565b90508181036060830152611f3381856119f7565b90508181036080830152611f478184611e1e565b9050969550505050505056fea26469706673582212203835581c6344b12728c44fa4d9e912cd60e64012c1b772bb703d1c36825c16fd64736f6c63430008180033"; -pub const NFT_SWAP_CONTRACT_BYTES: &str = "60a060405234801562000010575f80fd5b50604051620055a2380380620055a2833981810160405281019062000036919062000147565b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603620000a7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200009e90620001fb565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff1681525050506200021b565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6200011182620000e6565b9050919050565b620001238162000105565b81146200012e575f80fd5b50565b5f81519050620001418162000118565b92915050565b5f602082840312156200015f576200015e620000e2565b5b5f6200016e8482850162000131565b91505092915050565b5f82825260208201905092915050565b7f66656541646472657373206d757374206e6f74206265207a65726f20616464725f8201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b5f620001e360238362000177565b9150620001f08262000187565b604082019050919050565b5f6020820190508181035f8301526200021481620001d5565b9050919050565b608051615360620002425f395f8181612aef01528181612b8a0152612f4801526153605ff3fe608060405260043610610113575f3560e01c80639b4603f21161009f578063cc90c19911610063578063cc90c1991461038e578063d6a71eb4146103b6578063e06cf966146103de578063efccb9eb14610408578063f23a6e611461044657610113565b80639b4603f2146102be578063b27e46fb146102da578063bc197c8114610302578063c8d9009b1461033e578063c92cd12d1461036657610113565b8063150b7a02116100e6578063150b7a02146101cb5780633e6af5f21461020757806346b95ac71461022f57806365e266171461026e5780636e6bf6d21461029657610113565b806301ffc9a71461011757806305ec158d146101535780630f235fce1461017b578063146e5b24146101a3575b5f80fd5b348015610122575f80fd5b5061013d6004803603810190610138919061386f565b610482565b60405161014a91906138b4565b60405180910390f35b34801561015e575f80fd5b506101796004803603810190610174919061398d565b610563565b005b348015610186575f80fd5b506101a1600480360381019061019c9190613a2a565b610823565b005b3480156101ae575f80fd5b506101c960048036038101906101c49190613ab3565b610add565b005b3480156101d6575f80fd5b506101f160048036038101906101ec9190613bb1565b610cc3565b6040516101fe9190613c44565b60405180910390f35b348015610212575f80fd5b5061022d60048036038101906102289190613ab3565b611112565b005b34801561023a575f80fd5b5061025560048036038101906102509190613c5d565b611423565b6040516102659493929190613d53565b60405180910390f35b348015610279575f80fd5b50610294600480360381019061028f9190613ab3565b611485565b005b3480156102a1575f80fd5b506102bc60048036038101906102b79190613a2a565b6118e9565b005b6102d860048036038101906102d39190613dc0565b611ba4565b005b3480156102e5575f80fd5b5061030060048036038101906102fb919061398d565b611eda565b005b34801561030d575f80fd5b5061032860048036038101906103239190613eb2565b612199565b6040516103359190613c44565b60405180910390f35b348015610349575f80fd5b50610364600480360381019061035f9190613a2a565b6121d5565b005b348015610371575f80fd5b5061038c6004803603810190610387919061398d565b6124fe565b005b348015610399575f80fd5b506103b460048036038101906103af9190613ab3565b61282c565b005b3480156103c1575f80fd5b506103dc60048036038101906103d79190613f89565b612bdc565b005b3480156103e9575f80fd5b506103f2612f46565b6040516103ff919061405c565b60405180910390f35b348015610413575f80fd5b5061042e60048036038101906104299190613c5d565b612f6a565b60405161043d939291906140bb565b60405180910390f35b348015610451575f80fd5b5061046c600480360381019061046791906140f0565b612fb6565b6040516104799190613c44565b60405180910390f35b5f7f4e2312e0000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061054c57507f150b7a02000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b8061055c575061055b8261344a565b5b9050919050565b6001600381111561057757610576613ce0565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff1660038111156105a9576105a8613ce0565b5b146105e9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105e090614206565b60405180910390fd5b5f600387336002896040516020016106019190614244565b60405160208183030381529060405260405161061d91906142ca565b602060405180830381855afa158015610638573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061065b91906142f4565b888888886040516020016106759796959493929190614384565b60405160208183030381529060405260405161069191906142ca565b602060405180830381855afa1580156106ac573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610736576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161072d9061444e565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff0219169083600381111561076c5761076b613ce0565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd73886040516107a0919061447b565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016107eb94939291906144d6565b5f604051808303815f87803b158015610802575f80fd5b505af1158015610814573d5f803e3d5ffd5b50505050505050505050505050565b6001600381111561083757610836613ce0565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561086957610868613ce0565b5b146108a9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108a090614206565b60405180910390fd5b5f60038633878787876040516020016108c79695949392919061452c565b6040516020818303038152906040526040516108e391906142ca565b602060405180830381855afa1580156108fe573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610988576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161097f9061444e565b60405180910390fd5b5f808881526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff164210156109f3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109ea9061460b565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff02191690836003811115610a2957610a28613ce0565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad1921907287604051610a5d919061447b565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b8152600401610aa693929190614629565b5f604051808303815f87803b158015610abd575f80fd5b505af1158015610acf573d5f803e3d5ffd5b505050505050505050505050565b60016004811115610af157610af0613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff166004811115610b2457610b23613ce0565b5b14610b64576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b5b90614206565b60405180910390fd5b5f600387878733888888604051602001610b84979695949392919061465e565b604051602081830303815290604052604051610ba091906142ca565b602060405180830381855afa158015610bbb573d5f803e3d5ffd5b5050506040515160601b905060015f8981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610c46576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c3d9061444e565b60405180910390fd5b600260015f8a81526020019081526020015f205f01601c6101000a81548160ff02191690836004811115610c7d57610c7c613ce0565b5b02179055507f9c45e43e2ef051f70491ffd5221bf02ab37e1324128714ef9610df5f24fc9fb588604051610cb1919061447b565b60405180910390a15050505050505050565b5f808383810190610cd49190614807565b90505f6003811115610ce957610ce8613ce0565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff166003811115610d1e57610d1d613ce0565b5b14610d5e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d55906148a2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff1603610dd0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dc79061490a565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603610e42576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e3990614972565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610eb4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610eab90614a00565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1614610f22576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f1990614a68565b60405180910390fd5b610f2f81602001516134b3565b15610f6f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f6690614ad0565b60405180910390fd5b5f60038260200151888460600151856080015186604001518b604051602001610f9d9695949392919061452c565b604051602081830303815290604052604051610fb991906142ca565b602060405180830381855afa158015610fd4573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff1681526020016001600381111561102457611023613ce0565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff021916908360038111156110bb576110ba613ce0565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f01516040516110f5919061447b565b60405180910390a163150b7a0260e01b9250505095945050505050565b6001600481111561112657611125613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff16600481111561115957611158613ce0565b5b14611199576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161119090614206565b60405180910390fd5b5f6003878787336002896040516020016111b39190614244565b6040516020818303038152906040526040516111cf91906142ca565b602060405180830381855afa1580156111ea573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061120d91906142f4565b8888604051602001611225979695949392919061465e565b60405160208183030381529060405260405161124191906142ca565b602060405180830381855afa15801561125c573d5f803e3d5ffd5b5050506040515160601b905060015f8981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146112e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016112de9061444e565b60405180910390fd5b600460015f8a81526020019081526020015f205f01601c6101000a81548160ff0219169083600481111561131e5761131d613ce0565b5b02179055507f45169a52eef651b20a81474b50b8a5d83225225fcd097ef3cf7952d9ab304f278885604051611354929190614aee565b60405180910390a15f86886113699190614b42565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036113e7573373ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f193505050501580156113e1573d5f803e3d5ffd5b50611418565b5f83905061141633838373ffffffffffffffffffffffffffffffffffffffff166134c49092919063ffffffff16565b505b505050505050505050565b6001602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900463ffffffff1690805f0160189054906101000a900463ffffffff1690805f01601c9054906101000a900460ff16905084565b6001600481111561149957611498613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff1660048111156114cc576114cb613ce0565b5b148061151c5750600260048111156114e7576114e6613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff16600481111561151a57611519613ce0565b5b145b61155b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161155290614be5565b60405180910390fd5b5f60038787873388888860405160200161157b979695949392919061465e565b60405160208183030381529060405260405161159791906142ca565b602060405180830381855afa1580156115b2573d5f803e3d5ffd5b5050506040515160601b905060015f8981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461163d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116349061444e565b60405180910390fd5b6002600481111561165157611650613ce0565b5b60015f8a81526020019081526020015f205f01601c9054906101000a900460ff16600481111561168457611683613ce0565b5b036116f65760015f8981526020019081526020015f205f0160189054906101000a900463ffffffff1663ffffffff164210156116f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116ec9061460b565b60405180910390fd5b5b6001600481111561170a57611709613ce0565b5b60015f8a81526020019081526020015f205f01601c9054906101000a900460ff16600481111561173d5761173c613ce0565b5b036117af5760015f8981526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff164210156117ae576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016117a590614c73565b60405180910390fd5b5b600460015f8a81526020019081526020015f205f01601c6101000a81548160ff021916908360048111156117e6576117e5613ce0565b5b02179055507fbdd7a4be6d82798a500b59077706b12d3f45acf5504828919f92501307b2b9538860405161181a919061447b565b60405180910390a15f868861182f9190614b42565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036118ad573373ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f193505050501580156118a7573d5f803e3d5ffd5b506118de565b5f8390506118dc33838373ffffffffffffffffffffffffffffffffffffffff166134c49092919063ffffffff16565b505b505050505050505050565b600160038111156118fd576118fc613ce0565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561192f5761192e613ce0565b5b1461196f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161196690614206565b60405180910390fd5b5f600386336002886040516020016119879190614244565b6040516020818303038152906040526040516119a391906142ca565b602060405180830381855afa1580156119be573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906119e191906142f4565b8787876040516020016119f99695949392919061452c565b604051602081830303815290604052604051611a1591906142ca565b602060405180830381855afa158015611a30573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614611aba576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611ab19061444e565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff02191690836003811115611af057611aef613ce0565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd7387604051611b24919061447b565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b8152600401611b6d93929190614629565b5f604051808303815f87803b158015611b84575f80fd5b505af1158015611b96573d5f803e3d5ffd5b505050505050505050505050565b5f6004811115611bb757611bb6613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff166004811115611bea57611be9613ce0565b5b14611c2a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c2190614d01565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603611c98576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c8f90614d8f565b60405180910390fd5b5f3411611cda576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611cd190614e1d565b60405180910390fd5b853411611d1c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611d1390614eab565b60405180910390fd5b5f60038734611d2b9190614ec9565b88883389895f604051602001611d47979695949392919061465e565b604051602081830303815290604052604051611d6391906142ca565b602060405180830381855afa158015611d7e573d5f803e3d5ffd5b5050506040515160601b90506040518060800160405280826bffffffffffffffffffffffff191681526020018463ffffffff1681526020018363ffffffff16815260200160016004811115611dd657611dd5613ce0565b5b81525060015f8a81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548163ffffffff021916908363ffffffff1602179055506060820151815f01601c6101000a81548160ff02191690836004811115611e9157611e90613ce0565b5b02179055509050507ffc6cdccd1d98ded12074a9ebc7f6ab74fed1814ff57f4fb5202464d8938bd93588604051611ec8919061447b565b60405180910390a15050505050505050565b60016003811115611eee57611eed613ce0565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115611f2057611f1f613ce0565b5b14611f60576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611f5790614206565b60405180910390fd5b5f600387338888888888604051602001611f809796959493929190614384565b604051602081830303815290604052604051611f9c91906142ca565b602060405180830381855afa158015611fb7573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614612041576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016120389061444e565b60405180910390fd5b5f808981526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff164210156120ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016120a39061460b565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff021916908360038111156120e2576120e1613ce0565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad1921907288604051612116919061447b565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b815260040161216194939291906144d6565b5f604051808303815f87803b158015612178575f80fd5b505af115801561218a573d5f803e3d5ffd5b50505050505050505050505050565b5f6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016121cc90614f46565b60405180910390fd5b600160038111156121e9576121e8613ce0565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561221b5761221a613ce0565b5b1461225b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161225290614206565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146122c9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016122c090614fae565b60405180910390fd5b5f60033387876002886040516020016122e29190614244565b6040516020818303038152906040526040516122fe91906142ca565b602060405180830381855afa158015612319573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061233c91906142f4565b87876040516020016123539695949392919061452c565b60405160208183030381529060405260405161236f91906142ca565b602060405180830381855afa15801561238a573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614612414576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161240b9061444e565b60405180910390fd5b60025f808981526020019081526020015f205f0160186101000a81548160ff0219169083600381111561244a57612449613ce0565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08760405161247e919061447b565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b81526004016124c793929190614629565b5f604051808303815f87803b1580156124de575f80fd5b505af11580156124f0573d5f803e3d5ffd5b505050505050505050505050565b6001600381111561251257612511613ce0565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff16600381111561254457612543613ce0565b5b14612584576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161257b90614206565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146125f2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016125e990614fae565b60405180910390fd5b5f600333888860028960405160200161260b9190614244565b60405160208183030381529060405260405161262791906142ca565b602060405180830381855afa158015612642573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061266591906142f4565b88888860405160200161267e9796959493929190614384565b60405160208183030381529060405260405161269a91906142ca565b602060405180830381855afa1580156126b5573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461273f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016127369061444e565b60405180910390fd5b60025f808a81526020019081526020015f205f0160186101000a81548160ff0219169083600381111561277557612774613ce0565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf0886040516127a9919061447b565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016127f494939291906144d6565b5f604051808303815f87803b15801561280b575f80fd5b505af115801561281d573d5f803e3d5ffd5b50505050505050505050505050565b600260048111156128405761283f613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff16600481111561287357612872613ce0565b5b146128b3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016128aa9061503c565b60405180910390fd5b5f600387873388886002896040516020016128ce9190614244565b6040516020818303038152906040526040516128ea91906142ca565b602060405180830381855afa158015612905573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061292891906142f4565b8860405160200161293f979695949392919061465e565b60405160208183030381529060405260405161295b91906142ca565b602060405180830381855afa158015612976573d5f803e3d5ffd5b5050506040515160601b905060015f8981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614612a01576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016129f89061444e565b60405180910390fd5b600360015f8a81526020019081526020015f205f01601c6101000a81548160ff02191690836004811115612a3857612a37613ce0565b5b02179055507f0d0da0df275f85bed3a5fe7ae79f3559341a3f9ccd8e010133438135bda00a878884604051612a6e929190614aee565b60405180910390a15f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603612b56573373ffffffffffffffffffffffffffffffffffffffff166108fc8890811502906040515f60405180830381858888f19350505050158015612aec573d5f803e3d5ffd5b507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166108fc8790811502906040515f60405180830381858888f19350505050158015612b50573d5f803e3d5ffd5b50612bd2565b5f829050612b8533898373ffffffffffffffffffffffffffffffffffffffff166134c49092919063ffffffff16565b612bd07f0000000000000000000000000000000000000000000000000000000000000000888373ffffffffffffffffffffffffffffffffffffffff166134c49092919063ffffffff16565b505b5050505050505050565b5f6004811115612bef57612bee613ce0565b5b60015f8b81526020019081526020015f205f01601c9054906101000a900460ff166004811115612c2257612c21613ce0565b5b14612c62576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612c59906150ca565b60405180910390fd5b5f8811612ca4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612c9b90615132565b60405180910390fd5b5f8711612ce6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612cdd9061519a565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603612d54576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612d4b90614d8f565b60405180910390fd5b5f60038989883389898d604051602001612d74979695949392919061465e565b604051602081830303815290604052604051612d9091906142ca565b602060405180830381855afa158015612dab573d5f803e3d5ffd5b5050506040515160601b90506040518060800160405280826bffffffffffffffffffffffff191681526020018463ffffffff1681526020018363ffffffff16815260200160016004811115612e0357612e02613ce0565b5b81525060015f8c81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548163ffffffff021916908363ffffffff1602179055506060820151815f01601c6101000a81548160ff02191690836004811115612ebe57612ebd613ce0565b5b02179055509050507ffc6cdccd1d98ded12074a9ebc7f6ab74fed1814ff57f4fb5202464d8938bd9358a604051612ef5919061447b565b60405180910390a15f879050612f3933308b8d612f129190614b42565b8473ffffffffffffffffffffffffffffffffffffffff16613543909392919063ffffffff16565b5050505050505050505050565b7f000000000000000000000000000000000000000000000000000000000000000081565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900463ffffffff1690805f0160189054906101000a900460ff16905083565b5f808383810190612fc79190614807565b90505f6003811115612fdc57612fdb613ce0565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff16600381111561301157613010613ce0565b5b14613051576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161304890615228565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff16036130c3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016130ba9061490a565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603613135576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161312c90614972565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146131a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161319e90614a00565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614613215576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161320c90614a68565b60405180910390fd5b5f8511613257576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161324e90615290565b60405180910390fd5b61326481602001516134b3565b156132a4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161329b90614ad0565b60405180910390fd5b5f60038260200151898460600151856080015186604001518c8c6040516020016132d49796959493929190614384565b6040516020818303038152906040526040516132f091906142ca565b602060405180830381855afa15801561330b573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff1681526020016001600381111561335b5761335a613ce0565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff021916908360038111156133f2576133f1613ce0565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f015160405161342c919061447b565b60405180910390a163f23a6e6160e01b925050509695505050505050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f80823b90505f8111915050919050565b61353e838473ffffffffffffffffffffffffffffffffffffffff1663a9059cbb85856040516024016134f79291906152ae565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506135c5565b505050565b6135bf848573ffffffffffffffffffffffffffffffffffffffff166323b872dd86868660405160240161357893929190614629565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506135c5565b50505050565b5f6135ef828473ffffffffffffffffffffffffffffffffffffffff1661365a90919063ffffffff16565b90505f81511415801561361357508080602001905181019061361191906152ff565b155b1561365557826040517f5274afe700000000000000000000000000000000000000000000000000000000815260040161364c919061405c565b60405180910390fd5b505050565b606061366783835f61366f565b905092915050565b6060814710156136b657306040517fcd7860590000000000000000000000000000000000000000000000000000000081526004016136ad919061405c565b60405180910390fd5b5f808573ffffffffffffffffffffffffffffffffffffffff1684866040516136de91906142ca565b5f6040518083038185875af1925050503d805f8114613718576040519150601f19603f3d011682016040523d82523d5f602084013e61371d565b606091505b509150915061372d868383613738565b925050509392505050565b60608261374d57613748826137c5565b6137bd565b5f825114801561377357505f8473ffffffffffffffffffffffffffffffffffffffff163b145b156137b557836040517f9996b3150000000000000000000000000000000000000000000000000000000081526004016137ac919061405c565b60405180910390fd5b8190506137be565b5b9392505050565b5f815111156137d75780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61384e8161381a565b8114613858575f80fd5b50565b5f8135905061386981613845565b92915050565b5f6020828403121561388457613883613812565b5b5f6138918482850161385b565b91505092915050565b5f8115159050919050565b6138ae8161389a565b82525050565b5f6020820190506138c75f8301846138a5565b92915050565b5f819050919050565b6138df816138cd565b81146138e9575f80fd5b50565b5f813590506138fa816138d6565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61392982613900565b9050919050565b6139398161391f565b8114613943575f80fd5b50565b5f8135905061395481613930565b92915050565b5f819050919050565b61396c8161395a565b8114613976575f80fd5b50565b5f8135905061398781613963565b92915050565b5f805f805f805f60e0888a0312156139a8576139a7613812565b5b5f6139b58a828b016138ec565b97505060206139c68a828b01613946565b96505060406139d78a828b016138ec565b95505060606139e88a828b016138ec565b94505060806139f98a828b01613946565b93505060a0613a0a8a828b01613979565b92505060c0613a1b8a828b01613979565b91505092959891949750929550565b5f805f805f8060c08789031215613a4457613a43613812565b5b5f613a5189828a016138ec565b9650506020613a6289828a01613946565b9550506040613a7389828a016138ec565b9450506060613a8489828a016138ec565b9350506080613a9589828a01613946565b92505060a0613aa689828a01613979565b9150509295509295509295565b5f805f805f805f60e0888a031215613ace57613acd613812565b5b5f613adb8a828b016138ec565b9750506020613aec8a828b01613979565b9650506040613afd8a828b01613979565b9550506060613b0e8a828b01613946565b9450506080613b1f8a828b016138ec565b93505060a0613b308a828b016138ec565b92505060c0613b418a828b01613946565b91505092959891949750929550565b5f80fd5b5f80fd5b5f80fd5b5f8083601f840112613b7157613b70613b50565b5b8235905067ffffffffffffffff811115613b8e57613b8d613b54565b5b602083019150836001820283011115613baa57613ba9613b58565b5b9250929050565b5f805f805f60808688031215613bca57613bc9613812565b5b5f613bd788828901613946565b9550506020613be888828901613946565b9450506040613bf988828901613979565b935050606086013567ffffffffffffffff811115613c1a57613c19613816565b5b613c2688828901613b5c565b92509250509295509295909350565b613c3e8161381a565b82525050565b5f602082019050613c575f830184613c35565b92915050565b5f60208284031215613c7257613c71613812565b5b5f613c7f848285016138ec565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b613cbc81613c88565b82525050565b5f63ffffffff82169050919050565b613cda81613cc2565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60058110613d1e57613d1d613ce0565b5b50565b5f819050613d2e82613d0d565b919050565b5f613d3d82613d21565b9050919050565b613d4d81613d33565b82525050565b5f608082019050613d665f830187613cb3565b613d736020830186613cd1565b613d806040830185613cd1565b613d8d6060830184613d44565b95945050505050565b613d9f81613cc2565b8114613da9575f80fd5b50565b5f81359050613dba81613d96565b92915050565b5f805f805f805f60e0888a031215613ddb57613dda613812565b5b5f613de88a828b016138ec565b9750506020613df98a828b01613979565b9650506040613e0a8a828b01613946565b9550506060613e1b8a828b016138ec565b9450506080613e2c8a828b016138ec565b93505060a0613e3d8a828b01613dac565b92505060c0613e4e8a828b01613dac565b91505092959891949750929550565b5f8083601f840112613e7257613e71613b50565b5b8235905067ffffffffffffffff811115613e8f57613e8e613b54565b5b602083019150836020820283011115613eab57613eaa613b58565b5b9250929050565b5f805f805f805f8060a0898b031215613ece57613ecd613812565b5b5f613edb8b828c01613946565b9850506020613eec8b828c01613946565b975050604089013567ffffffffffffffff811115613f0d57613f0c613816565b5b613f198b828c01613e5d565b9650965050606089013567ffffffffffffffff811115613f3c57613f3b613816565b5b613f488b828c01613e5d565b9450945050608089013567ffffffffffffffff811115613f6b57613f6a613816565b5b613f778b828c01613b5c565b92509250509295985092959890939650565b5f805f805f805f805f6101208a8c031215613fa757613fa6613812565b5b5f613fb48c828d016138ec565b9950506020613fc58c828d01613979565b9850506040613fd68c828d01613979565b9750506060613fe78c828d01613946565b9650506080613ff88c828d01613946565b95505060a06140098c828d016138ec565b94505060c061401a8c828d016138ec565b93505060e061402b8c828d01613dac565b92505061010061403d8c828d01613dac565b9150509295985092959850929598565b6140568161391f565b82525050565b5f60208201905061406f5f83018461404d565b92915050565b6004811061408657614085613ce0565b5b50565b5f81905061409682614075565b919050565b5f6140a582614089565b9050919050565b6140b58161409b565b82525050565b5f6060820190506140ce5f830186613cb3565b6140db6020830185613cd1565b6140e860408301846140ac565b949350505050565b5f805f805f8060a0878903121561410a57614109613812565b5b5f61411789828a01613946565b965050602061412889828a01613946565b955050604061413989828a01613979565b945050606061414a89828a01613979565b935050608087013567ffffffffffffffff81111561416b5761416a613816565b5b61417789828a01613b5c565b92509250509295509295509295565b5f82825260208201905092915050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520505f8201527f61796d656e7453656e7400000000000000000000000000000000000000000000602082015250565b5f6141f0602a83614186565b91506141fb82614196565b604082019050919050565b5f6020820190508181035f83015261421d816141e4565b9050919050565b5f819050919050565b61423e614239826138cd565b614224565b82525050565b5f61424f828461422d565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b5f5b8381101561428f578082015181840152602081019050614274565b5f8484015250505050565b5f6142a48261425e565b6142ae8185614268565b93506142be818560208601614272565b80840191505092915050565b5f6142d5828461429a565b915081905092915050565b5f815190506142ee816138d6565b92915050565b5f6020828403121561430957614308613812565b5b5f614316848285016142e0565b91505092915050565b5f8160601b9050919050565b5f6143358261431f565b9050919050565b5f6143468261432b565b9050919050565b61435e6143598261391f565b61433c565b82525050565b5f819050919050565b61437e6143798261395a565b614364565b82525050565b5f61438f828a61434d565b60148201915061439f828961434d565b6014820191506143af828861422d565b6020820191506143bf828761422d565b6020820191506143cf828661434d565b6014820191506143df828561436d565b6020820191506143ef828461436d565b60208201915081905098975050505050505050565b7f496e76616c6964207061796d656e7448617368000000000000000000000000005f82015250565b5f614438601383614186565b915061444382614404565b602082019050919050565b5f6020820190508181035f8301526144658161442c565b9050919050565b614475816138cd565b82525050565b5f60208201905061448e5f83018461446c565b92915050565b61449d8161395a565b82525050565b5f82825260208201905092915050565b50565b5f6144c15f836144a3565b91506144cc826144b3565b5f82019050919050565b5f60a0820190506144e95f83018761404d565b6144f6602083018661404d565b6145036040830185614494565b6145106060830184614494565b8181036080830152614521816144b6565b905095945050505050565b5f614537828961434d565b601482019150614547828861434d565b601482019150614557828761422d565b602082019150614567828661422d565b602082019150614577828561434d565b601482019150614587828461436d565b602082019150819050979650505050505050565b7f43757272656e742074696d657374616d70206469646e277420657863656564205f8201527f7061796d656e7420726566756e64206c6f636b2074696d650000000000000000602082015250565b5f6145f5603883614186565b91506146008261459b565b604082019050919050565b5f6020820190508181035f830152614622816145e9565b9050919050565b5f60608201905061463c5f83018661404d565b614649602083018561404d565b6146566040830184614494565b949350505050565b5f614669828a61436d565b602082019150614679828961436d565b602082019150614689828861434d565b601482019150614699828761434d565b6014820191506146a9828661422d565b6020820191506146b9828561422d565b6020820191506146c9828461434d565b60148201915081905098975050505050505050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b614728826146e2565b810181811067ffffffffffffffff82111715614747576147466146f2565b5b80604052505050565b5f614759613809565b9050614765828261471f565b919050565b5f60c0828403121561477f5761477e6146de565b5b61478960c0614750565b90505f614798848285016138ec565b5f8301525060206147ab84828501613946565b60208301525060406147bf84828501613946565b60408301525060606147d3848285016138ec565b60608301525060806147e7848285016138ec565b60808301525060a06147fb84828501613dac565b60a08301525092915050565b5f60c0828403121561481c5761481b613812565b5b5f6148298482850161476a565b91505092915050565b7f4d616b657220455243373231207061796d656e74206d75737420626520556e695f8201527f6e697469616c697a656400000000000000000000000000000000000000000000602082015250565b5f61488c602a83614186565b915061489782614832565b604082019050919050565b5f6020820190508181035f8301526148b981614880565b9050919050565b7f54616b6572206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f6148f4601e83614186565b91506148ff826148c0565b602082019050919050565b5f6020820190508181035f830152614921816148e8565b9050919050565b7f546f6b656e206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f61495c601e83614186565b915061496782614928565b602082019050919050565b5f6020820190508181035f83015261498981614950565b9050919050565b7f546f6b656e206164647265737320646f6573206e6f74206d617463682073656e5f8201527f6465720000000000000000000000000000000000000000000000000000000000602082015250565b5f6149ea602383614186565b91506149f582614990565b604082019050919050565b5f6020820190508181035f830152614a17816149de565b9050919050565b7f4f70657261746f72206d757374206265207468652073656e64657200000000005f82015250565b5f614a52601b83614186565b9150614a5d82614a1e565b602082019050919050565b5f6020820190508181035f830152614a7f81614a46565b9050919050565b7f54616b65722063616e6e6f74206265206120636f6e74726163740000000000005f82015250565b5f614aba601a83614186565b9150614ac582614a86565b602082019050919050565b5f6020820190508181035f830152614ae781614aae565b9050919050565b5f604082019050614b015f83018561446c565b614b0e602083018461446c565b9392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f614b4c8261395a565b9150614b578361395a565b9250828201905080821115614b6f57614b6e614b15565b5b92915050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520505f8201527f61796d656e7453656e74206f722054616b6572417070726f7665640000000000602082015250565b5f614bcf603b83614186565b9150614bda82614b75565b604082019050919050565b5f6020820190508181035f830152614bfc81614bc3565b9050919050565b7f43757272656e742074696d657374616d70206469646e277420657863656564205f8201527f7061796d656e74207072652d617070726f7665206c6f636b2074696d65000000602082015250565b5f614c5d603d83614186565b9150614c6882614c03565b604082019050919050565b5f6020820190508181035f830152614c8a81614c51565b9050919050565b7f54616b6572207061796d656e7420697320616c726561647920696e697469616c5f8201527f697a656400000000000000000000000000000000000000000000000000000000602082015250565b5f614ceb602483614186565b9150614cf682614c91565b604082019050919050565b5f6020820190508181035f830152614d1881614cdf565b9050919050565b7f5265636569766572206d757374206e6f74206265207a65726f206164647265735f8201527f7300000000000000000000000000000000000000000000000000000000000000602082015250565b5f614d79602183614186565b9150614d8482614d1f565b604082019050919050565b5f6020820190508181035f830152614da681614d6d565b9050919050565b7f4554482076616c7565206d7573742062652067726561746572207468616e207a5f8201527f65726f0000000000000000000000000000000000000000000000000000000000602082015250565b5f614e07602383614186565b9150614e1282614dad565b604082019050919050565b5f6020820190508181035f830152614e3481614dfb565b9050919050565b7f4554482076616c7565206d7573742062652067726561746572207468616e20645f8201527f6578206665650000000000000000000000000000000000000000000000000000602082015250565b5f614e95602683614186565b9150614ea082614e3b565b604082019050919050565b5f6020820190508181035f830152614ec281614e89565b9050919050565b5f614ed38261395a565b9150614ede8361395a565b9250828203905081811115614ef657614ef5614b15565b5b92915050565b7f4261746368207472616e7366657273206e6f7420737570706f727465640000005f82015250565b5f614f30601d83614186565b9150614f3b82614efc565b602082019050919050565b5f6020820190508181035f830152614f5d81614f24565b9050919050565b7f43616c6c6572206d75737420626520616e20454f4100000000000000000000005f82015250565b5f614f98601583614186565b9150614fa382614f64565b602082019050919050565b5f6020820190508181035f830152614fc581614f8c565b9050919050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520545f8201527f616b6572417070726f7665640000000000000000000000000000000000000000602082015250565b5f615026602c83614186565b915061503182614fcc565b604082019050919050565b5f6020820190508181035f8301526150538161501a565b9050919050565b7f4552433230207632207061796d656e7420697320616c726561647920696e69745f8201527f69616c697a656400000000000000000000000000000000000000000000000000602082015250565b5f6150b4602783614186565b91506150bf8261505a565b604082019050919050565b5f6020820190508181035f8301526150e1816150a8565b9050919050565b7f416d6f756e74206d757374206e6f74206265207a65726f0000000000000000005f82015250565b5f61511c601783614186565b9150615127826150e8565b602082019050919050565b5f6020820190508181035f83015261514981615110565b9050919050565b7f44657820666565206d757374206e6f74206265207a65726f00000000000000005f82015250565b5f615184601883614186565b915061518f82615150565b602082019050919050565b5f6020820190508181035f8301526151b181615178565b9050919050565b7f4d616b65722045524331313535207061796d656e74206d75737420626520556e5f8201527f696e697469616c697a6564000000000000000000000000000000000000000000602082015250565b5f615212602b83614186565b915061521d826151b8565b604082019050919050565b5f6020820190508181035f83015261523f81615206565b9050919050565b7f56616c7565206d7573742062652067726561746572207468616e2030000000005f82015250565b5f61527a601c83614186565b915061528582615246565b602082019050919050565b5f6020820190508181035f8301526152a78161526e565b9050919050565b5f6040820190506152c15f83018561404d565b6152ce6020830184614494565b9392505050565b6152de8161389a565b81146152e8575f80fd5b50565b5f815190506152f9816152d5565b92915050565b5f6020828403121561531457615313613812565b5b5f615321848285016152eb565b9150509291505056fea26469706673582212200d86b0f6898fb823c55626c3b02a7098bc8622606b092e0f458df6c86ce2967864736f6c63430008180033"; +pub const ERC20_TOKEN_BYTES: &str = include_str!("../../../mm2_test_helpers/contract_bytes/erc20_token_bytes"); +pub const SWAP_CONTRACT_BYTES: &str = include_str!("../../../mm2_test_helpers/contract_bytes/swap_contract_bytes"); +pub const WATCHERS_SWAP_CONTRACT_BYTES: &str = + include_str!("../../../mm2_test_helpers/contract_bytes/watchers_swap_contract_bytes"); +pub const ERC721_TEST_TOKEN_BYTES: &str = + include_str!("../../../mm2_test_helpers/contract_bytes/erc721_test_token_bytes"); +pub const ERC1155_TEST_TOKEN_BYTES: &str = + include_str!("../../../mm2_test_helpers/contract_bytes/erc1155_test_token_bytes"); +/// https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerNftV2.sol +pub const NFT_MAKER_SWAP_V2_BYTES: &str = + include_str!("../../../mm2_test_helpers/contract_bytes/nft_maker_swap_v2_bytes"); +/// https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerNftV2.sol +pub const MAKER_SWAP_V2_BYTES: &str = include_str!("../../../mm2_test_helpers/contract_bytes/maker_swap_v2_bytes"); +/// https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol +pub const TAKER_SWAP_V2_BYTES: &str = include_str!("../../../mm2_test_helpers/contract_bytes/taker_swap_v2_bytes"); pub trait CoinDockerOps { fn rpc_client(&self) -> &UtxoRpcClientEnum; @@ -127,13 +168,13 @@ pub trait CoinDockerOps { fn wait_ready(&self, expected_tx_version: i32) { let timeout = wait_until_ms(120000); loop { - match self.rpc_client().get_block_count().wait() { + match block_on_f01(self.rpc_client().get_block_count()) { Ok(n) => { if n > 1 { if let UtxoRpcClientEnum::Native(client) = self.rpc_client() { - let hash = client.get_block_hash(n).wait().unwrap(); - let block = client.get_block(hash).wait().unwrap(); - let coinbase = client.get_verbose_transaction(&block.tx[0]).wait().unwrap(); + let hash = block_on_f01(client.get_block_hash(n)).unwrap(); + let block = block_on_f01(client.get_block(hash)).unwrap(); + let coinbase = block_on_f01(client.get_verbose_transaction(&block.tx[0])).unwrap(); log!("Coinbase tx {:?} in block {}", coinbase, n); if coinbase.version == expected_tx_version { break; @@ -223,10 +264,11 @@ impl BchDockerOps { .build() .expect("valid address props"); - self.native_client() - .import_address(&address.to_string(), &address.to_string(), false) - .wait() - .unwrap(); + block_on_f01( + self.native_client() + .import_address(&address.to_string(), &address.to_string(), false), + ) + .unwrap(); let script_pubkey = Builder::build_p2pkh(&key_pair.public().address_hash().into()); @@ -242,9 +284,7 @@ impl BchDockerOps { slp_privkeys.push(*key_pair.private_ref()); } - let slp_genesis_tx = send_outputs_from_my_address(self.coin.clone(), bch_outputs) - .wait() - .unwrap(); + let slp_genesis_tx = block_on_f01(send_outputs_from_my_address(self.coin.clone(), bch_outputs)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: slp_genesis_tx.tx_hex(), confirmations: 1, @@ -252,7 +292,7 @@ impl BchDockerOps { wait_until: wait_until_sec(30), check_every: 1, }; - self.coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(self.coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let adex_slp = SlpToken::new( 8, @@ -271,7 +311,7 @@ impl BchDockerOps { wait_until: wait_until_sec(30), check_every: 1, }; - self.coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(self.coin.wait_for_confirmations(confirm_payment_input)).unwrap(); *SLP_TOKEN_OWNERS.lock().unwrap() = slp_privkeys; *SLP_TOKEN_ID.lock().unwrap() = slp_genesis_tx.tx_hash_as_bytes().as_slice().into(); } @@ -347,6 +387,70 @@ pub fn geth_docker_node<'a>(docker: &'a Cli, ticker: &'static str, port: u16) -> } } +#[allow(dead_code)] +pub fn sia_docker_node<'a>(docker: &'a Cli, ticker: &'static str, port: u16) -> DockerNode<'a> { + let image = + GenericImage::new(SIA_DOCKER_IMAGE, "latest").with_env_var("WALLETD_API_PASSWORD", "password".to_string()); + let args = vec![]; + let image = RunnableImage::from((image, args)) + .with_mapped_port((port, port)) + .with_container_name("sia-docker"); + let container = docker.run(image); + DockerNode { + container, + ticker: ticker.into(), + port, + } +} + +pub fn nucleus_node(docker: &'_ Cli, runtime_dir: PathBuf) -> DockerNode<'_> { + let nucleus_node_runtime_dir = runtime_dir.join("nucleus-testnet-data"); + assert!(nucleus_node_runtime_dir.exists()); + + let image = GenericImage::new(NUCLEUS_IMAGE, "latest") + .with_volume(nucleus_node_runtime_dir.to_str().unwrap(), "/root/.nucleus"); + let image = RunnableImage::from((image, vec![])).with_network("host"); + let container = docker.run(image); + + DockerNode { + container, + ticker: "NUCLEUS-TEST".to_owned(), + port: Default::default(), // This doesn't need to be the correct value as we are using the host network. + } +} + +pub fn atom_node(docker: &'_ Cli, runtime_dir: PathBuf) -> DockerNode<'_> { + let atom_node_runtime_dir = runtime_dir.join("atom-testnet-data"); + assert!(atom_node_runtime_dir.exists()); + + let (image, tag) = ATOM_IMAGE_WITH_TAG.rsplit_once(':').unwrap(); + let image = GenericImage::new(image, tag).with_volume(atom_node_runtime_dir.to_str().unwrap(), "/root/.gaia"); + let image = RunnableImage::from((image, vec![])).with_network("host"); + let container = docker.run(image); + + DockerNode { + container, + ticker: "ATOM-TEST".to_owned(), + port: Default::default(), // This doesn't need to be the correct value as we are using the host network. + } +} + +pub fn ibc_relayer_node(docker: &'_ Cli, runtime_dir: PathBuf) -> DockerNode<'_> { + let relayer_node_runtime_dir = runtime_dir.join("ibc-relayer-data"); + assert!(relayer_node_runtime_dir.exists()); + + let (image, tag) = IBC_RELAYER_IMAGE_WITH_TAG.rsplit_once(':').unwrap(); + let image = GenericImage::new(image, tag).with_volume(relayer_node_runtime_dir.to_str().unwrap(), "/root/.relayer"); + let image = RunnableImage::from((image, vec![])).with_network("host"); + let container = docker.run(image); + + DockerNode { + container, + ticker: Default::default(), // This isn't an asset node. + port: Default::default(), // This doesn't need to be the correct value as we are using the host network. + } +} + pub fn rmd160_from_priv(privkey: Secp256k1Secret) -> H160 { let secret = SecretKey::from_slice(privkey.as_slice()).unwrap(); let public = PublicKey::from_secret_key(&Secp256k1::new(), &secret); @@ -373,7 +477,7 @@ where match coin.as_ref().rpc_client { UtxoRpcClientEnum::Native(ref native) => { let my_address = coin.my_address().unwrap(); - native.import_address(&my_address, &my_address, false).wait().unwrap() + block_on_f01(native.import_address(&my_address, &my_address, false)).unwrap() }, UtxoRpcClientEnum::Electrum(_) => panic!("Expected NativeClient"), } @@ -493,9 +597,7 @@ where UtxoRpcClientEnum::Native(ref native) => native, UtxoRpcClientEnum::Electrum(_) => panic!("NativeClient expected"), }; - let mut addresses = native - .get_addresses_by_label(label) - .wait() + let mut addresses = block_on_f01(native.get_addresses_by_label(label)) .expect("!getaddressesbylabel") .into_iter(); match addresses.next() { @@ -517,22 +619,20 @@ pub fn fill_qrc20_address(coin: &Qrc20Coin, amount: BigDecimal, timeout: u64) { }; let from_addr = get_address_by_label(coin, QTUM_ADDRESS_LABEL); - let to_addr = coin.my_addr_as_contract_addr().compat().wait().unwrap(); + let to_addr = block_on_f01(coin.my_addr_as_contract_addr().compat()).unwrap(); let satoshis = sat_from_big_decimal(&amount, coin.as_ref().decimals).expect("!sat_from_big_decimal"); - let hash = client - .transfer_tokens( - &coin.contract_address, - &from_addr, - to_addr, - satoshis.into(), - coin.as_ref().decimals, - ) - .wait() - .expect("!transfer_tokens") - .txid; + let hash = block_on_f01(client.transfer_tokens( + &coin.contract_address, + &from_addr, + to_addr, + satoshis.into(), + coin.as_ref().decimals, + )) + .expect("!transfer_tokens") + .txid; - let tx_bytes = client.get_transaction_bytes(&hash).wait().unwrap(); + let tx_bytes = block_on_f01(client.get_transaction_bytes(&hash)).unwrap(); log!("{:02x}", tx_bytes); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx_bytes.0, @@ -541,7 +641,7 @@ pub fn fill_qrc20_address(coin: &Qrc20Coin, amount: BigDecimal, timeout: u64) { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); } /// Generate random privkey, create a QRC20 coin and fill it's address with the specified balance. @@ -649,9 +749,9 @@ where let timeout = wait_until_sec(timeout); if let UtxoRpcClientEnum::Native(client) = &coin.as_ref().rpc_client { - client.import_address(address, address, false).wait().unwrap(); - let hash = client.send_to_address(address, &amount).wait().unwrap(); - let tx_bytes = client.get_transaction_bytes(&hash).wait().unwrap(); + block_on_f01(client.import_address(address, address, false)).unwrap(); + let hash = block_on_f01(client.send_to_address(address, &amount)).unwrap(); + let tx_bytes = block_on_f01(client.get_transaction_bytes(&hash)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx_bytes.clone().0, confirmations: 1, @@ -659,13 +759,10 @@ where wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); log!("{:02x}", tx_bytes); loop { - let unspents = client - .list_unspent_impl(0, std::i32::MAX, vec![address.to_string()]) - .wait() - .unwrap(); + let unspents = block_on_f01(client.list_unspent_impl(0, std::i32::MAX, vec![address.to_string()])).unwrap(); if !unspents.is_empty() { break; } @@ -701,7 +798,7 @@ pub fn wait_for_estimate_smart_fee(timeout: u64) -> Result<(), String> { UtxoRpcClientEnum::Electrum(_) => panic!("Expected NativeClient"), }; while now_sec() < timeout { - if let Ok(res) = client.estimate_smart_fee(&None, 1).wait() { + if let Ok(res) = block_on_f01(client.estimate_smart_fee(&None, 1)) { if res.errors.is_empty() { *state = EstimateSmartFeeState::Ok; return Ok(()); @@ -968,37 +1065,12 @@ pub fn slp_supplied_node() -> MarketMakerIt { ]); let priv_key = get_prefilled_slp_privkey(); - let mm = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 9000, - "dht": "on", // Enable DHT without delay. - "passphrase": format!("0x{}", hex::encode(priv_key)), - "coins": coins, - "rpc_password": "pass", - "i_am_seed": true, - }), - "pass".to_string(), - None, - ) - .unwrap(); - - mm -} - -pub fn _solana_supplied_node() -> MarketMakerIt { - let coins = json! ([ - {"coin": "SOL-DEVNET","name": "solana","fname": "Solana","rpcport": 80,"mm2": 1,"required_confirmations": 1,"avg_blocktime": 0.25,"protocol": {"type": "SOLANA"}}, - {"coin":"USDC-SOL-DEVNET","protocol":{"type":"SPLTOKEN","protocol_data":{"decimals":6,"token_contract_address":"4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU","platform":"SOL-DEVNET"}},"mm2": 1}, - {"coin":"ADEX-SOL-DEVNET","protocol":{"type":"SPLTOKEN","protocol_data":{"decimals":9,"token_contract_address":"5tSm6PqMosy1rz1AqV3kD28yYT5XqZW3QYmZommuFiPJ","platform":"SOL-DEVNET"}},"mm2": 1}, - ]); - MarketMakerIt::start( json! ({ "gui": "nogui", "netid": 9000, "dht": "on", // Enable DHT without delay. - "passphrase": "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron", + "passphrase": format!("0x{}", hex::encode(priv_key)), "coins": coins, "rpc_password": "pass", "i_am_seed": true, @@ -1069,9 +1141,58 @@ async fn get_current_gas_limit(web3: &Web3) { } } +pub fn prepare_ibc_channels(container_id: &str) { + let exec = |args: &[&str]| { + Command::new("docker") + .args(["exec", container_id]) + .args(args) + .output() + .unwrap(); + }; + + exec(&["rly", "transact", "clients", "nucleus-atom", "--override"]); + // It takes a couple of seconds for nodes to get into the right state after updating clients. + // Wait for 5 just to make sure. + thread::sleep(Duration::from_secs(5)); + + exec(&["rly", "transact", "link", "nucleus-atom"]); +} + +pub fn wait_until_relayer_container_is_ready(container_id: &str) { + const Q_RESULT: &str = "0: nucleus-atom -> chns(✔) clnts(✔) conn(✔) (nucleus-testnet<>cosmoshub-testnet)"; + + let mut attempts = 0; + loop { + let mut docker = Command::new("docker"); + docker.arg("exec").arg(container_id).args(["rly", "paths", "list"]); + + log!("Running <<{docker:?}>>."); + + let output = docker.stderr(Stdio::inherit()).output().unwrap(); + let output = String::from_utf8(output.stdout).unwrap(); + let output = output.trim(); + + if output == Q_RESULT { + break; + } + attempts += 1; + + log!("Expected output {Q_RESULT}, received {output}."); + if attempts > 10 { + panic!("{}", "Reached max attempts for <<{docker:?}>>."); + } else { + log!("Asking for relayer node status again.."); + } + + thread::sleep(Duration::from_secs(2)); + } +} + pub fn init_geth_node() { unsafe { block_on(get_current_gas_limit(&GETH_WEB3)); + let gas_price = block_on(GETH_WEB3.eth().gas_price()).unwrap(); + log!("Current gas price: {:?}", gas_price); let accounts = block_on(GETH_WEB3.eth().accounts()).unwrap(); GETH_ACCOUNT = accounts[0]; log!("GETH ACCOUNT {:?}", GETH_ACCOUNT); @@ -1145,6 +1266,105 @@ pub fn init_geth_node() { thread::sleep(Duration::from_millis(100)); } + let tx_request_deploy_maker_swap_contract_v2 = TransactionRequest { + from: GETH_ACCOUNT, + to: None, + gas: None, + gas_price: None, + value: None, + data: Some(hex::decode(MAKER_SWAP_V2_BYTES).unwrap().into()), + nonce: None, + condition: None, + transaction_type: None, + access_list: None, + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + }; + let deploy_maker_swap_v2_tx_hash = block_on( + GETH_WEB3 + .eth() + .send_transaction(tx_request_deploy_maker_swap_contract_v2), + ) + .unwrap(); + log!( + "Sent deploy maker swap v2 contract transaction {:?}", + deploy_maker_swap_v2_tx_hash + ); + + loop { + let deploy_maker_swap_v2_tx_receipt = + match block_on(GETH_WEB3.eth().transaction_receipt(deploy_maker_swap_v2_tx_hash)) { + Ok(receipt) => receipt, + Err(_) => { + thread::sleep(Duration::from_millis(100)); + continue; + }, + }; + + if let Some(receipt) = deploy_maker_swap_v2_tx_receipt { + GETH_MAKER_SWAP_V2 = receipt.contract_address.unwrap(); + log!( + "GETH_MAKER_SWAP_V2 contract address: {:?}, receipt.status: {:?}", + GETH_MAKER_SWAP_V2, + receipt.status + ); + break; + } + thread::sleep(Duration::from_millis(100)); + } + + let dex_fee_addr = addr_from_raw_pubkey(&DEX_FEE_ADDR_RAW_PUBKEY).unwrap(); + let dex_fee_addr = Token::Address(dex_fee_addr); + let params = ethabi::encode(&[dex_fee_addr]); + let taker_swap_v2_data = format!("{}{}", TAKER_SWAP_V2_BYTES, hex::encode(params)); + + let tx_request_deploy_taker_swap_contract_v2 = TransactionRequest { + from: GETH_ACCOUNT, + to: None, + gas: None, + gas_price: None, + value: None, + data: Some(hex::decode(taker_swap_v2_data).unwrap().into()), + nonce: None, + condition: None, + transaction_type: None, + access_list: None, + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + }; + let deploy_taker_swap_v2_tx_hash = block_on( + GETH_WEB3 + .eth() + .send_transaction(tx_request_deploy_taker_swap_contract_v2), + ) + .unwrap(); + log!( + "Sent deploy taker swap v2 contract transaction {:?}", + deploy_taker_swap_v2_tx_hash + ); + + loop { + let deploy_taker_swap_v2_tx_receipt = + match block_on(GETH_WEB3.eth().transaction_receipt(deploy_taker_swap_v2_tx_hash)) { + Ok(receipt) => receipt, + Err(_) => { + thread::sleep(Duration::from_millis(100)); + continue; + }, + }; + + if let Some(receipt) = deploy_taker_swap_v2_tx_receipt { + GETH_TAKER_SWAP_V2 = receipt.contract_address.unwrap(); + log!( + "GETH_TAKER_SWAP_V2 contract address: {:?}, receipt.status: {:?}", + GETH_TAKER_SWAP_V2, + receipt.status + ); + break; + } + thread::sleep(Duration::from_millis(100)); + } + let tx_request_deploy_watchers_swap_contract = TransactionRequest { from: GETH_ACCOUNT, to: None, @@ -1182,7 +1402,101 @@ pub fn init_geth_node() { if let Some(receipt) = deploy_watchers_swap_tx_receipt { GETH_WATCHERS_SWAP_CONTRACT = receipt.contract_address.unwrap(); - log!("GETH_WATCHERS_SWAP_CONTRACT {:?}", GETH_SWAP_CONTRACT); + log!("GETH_WATCHERS_SWAP_CONTRACT {:?}", GETH_WATCHERS_SWAP_CONTRACT); + break; + } + thread::sleep(Duration::from_millis(100)); + } + + let tx_request_deploy_nft_maker_swap_v2_contract = TransactionRequest { + from: GETH_ACCOUNT, + to: None, + gas: None, + gas_price: None, + value: None, + data: Some(hex::decode(NFT_MAKER_SWAP_V2_BYTES).unwrap().into()), + nonce: None, + condition: None, + transaction_type: None, + access_list: None, + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + }; + let deploy_nft_maker_swap_v2_tx_hash = block_on( + GETH_WEB3 + .eth() + .send_transaction(tx_request_deploy_nft_maker_swap_v2_contract), + ) + .unwrap(); + log!( + "Sent deploy nft maker swap v2 contract transaction {:?}", + deploy_nft_maker_swap_v2_tx_hash + ); + + loop { + let deploy_nft_maker_swap_v2_tx_receipt = + match block_on(GETH_WEB3.eth().transaction_receipt(deploy_nft_maker_swap_v2_tx_hash)) { + Ok(receipt) => receipt, + Err(_) => { + thread::sleep(Duration::from_millis(100)); + continue; + }, + }; + + if let Some(receipt) = deploy_nft_maker_swap_v2_tx_receipt { + GETH_NFT_MAKER_SWAP_V2 = receipt.contract_address.unwrap(); + log!( + "GETH_NFT_MAKER_SWAP_V2 contact address: {:?}, receipt.status: {:?}", + GETH_NFT_MAKER_SWAP_V2, + receipt.status + ); + break; + } + thread::sleep(Duration::from_millis(100)); + } + + let tx_request_deploy_nft_maker_swap_v2_contract = TransactionRequest { + from: GETH_ACCOUNT, + to: None, + gas: None, + gas_price: None, + value: None, + data: Some(hex::decode(NFT_MAKER_SWAP_V2_BYTES).unwrap().into()), + nonce: None, + condition: None, + transaction_type: None, + access_list: None, + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + }; + let deploy_nft_maker_swap_v2_tx_hash = block_on( + GETH_WEB3 + .eth() + .send_transaction(tx_request_deploy_nft_maker_swap_v2_contract), + ) + .unwrap(); + log!( + "Sent deploy nft maker swap v2 contract transaction {:?}", + deploy_nft_maker_swap_v2_tx_hash + ); + + loop { + let deploy_nft_maker_swap_v2_tx_receipt = + match block_on(GETH_WEB3.eth().transaction_receipt(deploy_nft_maker_swap_v2_tx_hash)) { + Ok(receipt) => receipt, + Err(_) => { + thread::sleep(Duration::from_millis(100)); + continue; + }, + }; + + if let Some(receipt) = deploy_nft_maker_swap_v2_tx_receipt { + GETH_NFT_MAKER_SWAP_V2 = receipt.contract_address.unwrap(); + log!( + "GETH_NFT_MAKER_SWAP_V2 {:?}, receipt.status {:?}", + GETH_NFT_MAKER_SWAP_V2, + receipt.status + ); break; } thread::sleep(Duration::from_millis(100)); @@ -1266,46 +1580,15 @@ pub fn init_geth_node() { thread::sleep(Duration::from_millis(100)); } - let dex_fee_address = Token::Address(geth_account()); - let params = ethabi::encode(&[dex_fee_address]); - let nft_swap_data = format!("{}{}", NFT_SWAP_CONTRACT_BYTES, hex::encode(params)); - - let tx_request_deploy_nft_swap_contract = TransactionRequest { - from: GETH_ACCOUNT, - to: None, - gas: None, - gas_price: None, - value: None, - data: Some(hex::decode(nft_swap_data).unwrap().into()), - nonce: None, - condition: None, - transaction_type: None, - access_list: None, - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - }; - let deploy_nft_swap_tx_hash = - block_on(GETH_WEB3.eth().send_transaction(tx_request_deploy_nft_swap_contract)).unwrap(); - log!("Sent deploy nft swap contract transaction {:?}", deploy_swap_tx_hash); - - loop { - let deploy_nft_swap_tx_receipt = - match block_on(GETH_WEB3.eth().transaction_receipt(deploy_nft_swap_tx_hash)) { - Ok(receipt) => receipt, - Err(_) => { - thread::sleep(Duration::from_millis(100)); - continue; - }, - }; - - if let Some(receipt) = deploy_nft_swap_tx_receipt { - GETH_NFT_SWAP_CONTRACT = receipt.contract_address.unwrap(); - log!("GETH_NFT_SWAP_CONTRACT {:?}", GETH_SWAP_CONTRACT); - break; - } - thread::sleep(Duration::from_millis(100)); + #[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] + { + SEPOLIA_ETOMIC_MAKER_NFT_SWAP_V2 = + EthAddress::from_str("0x9eb88cd58605d8fb9b14652d6152727f7e95fb4d").unwrap(); + SEPOLIA_ERC20_CONTRACT = EthAddress::from_str("0xF7b5F8E8555EF7A743f24D3E974E23A3C6cB6638").unwrap(); + SEPOLIA_TAKER_SWAP_V2 = EthAddress::from_str("0x7Cc9F2c1c3B797D09B9d1CCd7FDcD2539a4b3874").unwrap(); + // deploy tx https://sepolia.etherscan.io/tx/0x6f743d79ecb806f5899a6a801083e33eba9e6f10726af0873af9f39883db7f11 + SEPOLIA_MAKER_SWAP_V2 = EthAddress::from_str("0xf9000589c66Df3573645B59c10aa87594Edc318F").unwrap(); } - let alice_passphrase = get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); let alice_keypair = key_pair_from_seed(&alice_passphrase).unwrap(); let alice_eth_addr = addr_from_raw_pubkey(alice_keypair.public()).unwrap(); diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index 330dec30de..2a253760bc 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -12,17 +12,16 @@ use coins::TxFeeDetails; use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash, TransactionEnum, WithdrawRequest}; -use common::{block_on, executor::Timer, get_utc_timestamp, now_sec, wait_until_sec}; +use common::{block_on, block_on_f01, executor::Timer, get_utc_timestamp, now_sec, wait_until_sec}; use crypto::privkey::key_pair_from_seed; use crypto::{CryptoCtx, DerivationPath, KeyPairPolicy}; -use futures01::Future; use http::StatusCode; use mm2_number::{BigDecimal, BigRational, MmNumber}; use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, disable_coin, disable_coin_err, enable_eth_coin, enable_eth_with_tokens_v2, erc20_dev_conf, eth_dev_conf, get_locked_amount, kmd_conf, max_maker_vol, mm_dump, mycoin1_conf, mycoin_conf, set_price, start_swaps, wait_for_swap_contract_negotiation, wait_for_swap_negotiation_failure, - MarketMakerIt, Mm2TestConf}; + MarketMakerIt, Mm2TestConf, DEFAULT_RPC_PASSWORD}; use mm2_test_helpers::{get_passphrase, structs::*}; use serde_json::Value as Json; use std::collections::{HashMap, HashSet}; @@ -51,7 +50,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); + let tx = block_on(coin.send_taker_payment(taker_payment_args)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx.tx_hex(), @@ -60,7 +59,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, @@ -81,7 +80,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -113,7 +112,9 @@ fn test_for_non_existent_tx_hex_utxo() { wait_until: timeout, check_every: 1, }; - let actual = coin.wait_for_confirmations(confirm_payment_input).wait().err().unwrap(); + let actual = block_on_f01(coin.wait_for_confirmations(confirm_payment_input)) + .err() + .unwrap(); assert!(actual.contains( "Tx d342ff9da528a2e262bddf2b6f9a27d1beb7aeb03f0fc8d9eac2987266447e44 was not found on chain after 10 tries" )); @@ -138,7 +139,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let tx = block_on(coin.send_maker_payment(maker_payment_args)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx.tx_hex(), @@ -147,7 +148,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, @@ -168,7 +169,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -207,7 +208,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); + let tx = block_on(coin.send_taker_payment(taker_payment_args)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx.tx_hex(), @@ -216,7 +217,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let maker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &tx.tx_hex(), time_lock, @@ -236,7 +237,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -275,7 +276,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let tx = block_on(coin.send_maker_payment(maker_payment_args)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx.tx_hex(), @@ -284,7 +285,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let taker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &tx.tx_hex(), time_lock, @@ -304,7 +305,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -346,7 +347,7 @@ fn test_one_hundred_maker_payments_in_a_row_native() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let tx = block_on(coin.send_maker_payment(maker_payment_args)).unwrap(); if let TransactionEnum::UtxoTx(tx) = tx { unspents.push(UnspentInfo { outpoint: OutPoint { @@ -2472,16 +2473,12 @@ fn test_maker_order_should_not_kick_start_and_appear_in_orderbook_if_balance_is_ bob_conf["log"] = mm_bob.folder.join("mm2_dup.log").to_str().unwrap().into(); block_on(mm_bob.stop()).unwrap(); - let withdraw = coin - .withdraw(WithdrawRequest::new_max( - "MYCOIN".to_string(), - "RRYmiZSDo3UdHHqj1rLKf8cbJroyv9NxXw".to_string(), - )) - .wait() - .unwrap(); - coin.send_raw_tx(&hex::encode(&withdraw.tx.tx_hex().unwrap().0)) - .wait() - .unwrap(); + let withdraw = block_on_f01(coin.withdraw(WithdrawRequest::new_max( + "MYCOIN".to_string(), + "RRYmiZSDo3UdHHqj1rLKf8cbJroyv9NxXw".to_string(), + ))) + .unwrap(); + block_on_f01(coin.send_raw_tx(&hex::encode(&withdraw.tx.tx_hex().unwrap().0))).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: withdraw.tx.tx_hex().unwrap().0.to_owned(), confirmations: 1, @@ -2489,7 +2486,7 @@ fn test_maker_order_should_not_kick_start_and_appear_in_orderbook_if_balance_is_ wait_until: wait_until_sec(10), check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let mm_bob_dup = MarketMakerIt::start(bob_conf, "pass".to_string(), None).unwrap(); let (_bob_dup_dump_log, _bob_dup_dump_dashboard) = mm_dump(&mm_bob_dup.log_path); @@ -5260,127 +5257,6 @@ fn test_sell_min_volume_dust() { assert!(!rc.0.is_success(), "!sell: {}", rc.1); } -#[test] -fn test_enable_eth_erc20_coins_with_enable_hd() { - const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; - - let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); - let swap_contract = format!("0x{}", hex::encode(swap_contract())); - - // Withdraw from HD account 0, change address 0, index 0 - let path_to_address = HDAccountAddressId::default(); - let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); - let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); - log!("Alice log path: {}", mm_hd.log_path.display()); - - let eth_enable = block_on(enable_eth_with_tokens_v2( - &mm_hd, - "ETH", - &["ERC20DEV"], - &swap_contract, - &[GETH_RPC_URL], - 60, - Some(path_to_address), - )); - let activation_result = match eth_enable { - EthWithTokensActivationResult::HD(hd) => hd, - _ => panic!("Expected EthWithTokensActivationResult::HD"), - }; - let balance = match activation_result.wallet_balance { - EnableCoinBalanceMap::HD(hd) => hd, - _ => panic!("Expected EnableCoinBalance::HD"), - }; - let account = balance.accounts.get(0).expect("Expected account at index 0"); - assert_eq!( - account.addresses[0].address, - "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93" - ); - assert_eq!(account.addresses[0].balance.len(), 2); - assert!(account.addresses[0].balance.contains_key("ETH")); - assert!(account.addresses[0].balance.contains_key("ERC20DEV")); - - block_on(mm_hd.stop()).unwrap(); - - // Enable HD account 0, change address 0, index 1 - let path_to_address = HDAccountAddressId { - account_id: 0, - chain: Bip44Chain::External, - address_id: 1, - }; - let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); - let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); - log!("Alice log path: {}", mm_hd.log_path.display()); - - let eth_enable = block_on(enable_eth_with_tokens_v2( - &mm_hd, - "ETH", - &["ERC20DEV"], - &swap_contract, - &[GETH_RPC_URL], - 60, - Some(path_to_address), - )); - let activation_result = match eth_enable { - EthWithTokensActivationResult::HD(hd) => hd, - _ => panic!("Expected EthWithTokensActivationResult::HD"), - }; - let balance = match activation_result.wallet_balance { - EnableCoinBalanceMap::HD(hd) => hd, - _ => panic!("Expected EnableCoinBalance::HD"), - }; - let account = balance.accounts.get(0).expect("Expected account at index 0"); - assert_eq!( - account.addresses[1].address, - "0xDe841899aB4A22E23dB21634e54920aDec402397" - ); - assert_eq!(account.addresses[0].balance.len(), 2); - assert!(account.addresses[0].balance.contains_key("ETH")); - assert!(account.addresses[0].balance.contains_key("ERC20DEV")); - - block_on(mm_hd.stop()).unwrap(); - - // Enable HD account 77, change address 0, index 7 - let path_to_address = HDAccountAddressId { - account_id: 77, - chain: Bip44Chain::External, - address_id: 7, - }; - let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); - let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); - log!("Alice log path: {}", mm_hd.log_path.display()); - - let eth_enable = block_on(enable_eth_with_tokens_v2( - &mm_hd, - "ETH", - &["ERC20DEV"], - &swap_contract, - &[GETH_RPC_URL], - 60, - Some(path_to_address), - )); - let activation_result = match eth_enable { - EthWithTokensActivationResult::HD(hd) => hd, - _ => panic!("Expected EthWithTokensActivationResult::HD"), - }; - let balance = match activation_result.wallet_balance { - EnableCoinBalanceMap::HD(hd) => hd, - _ => panic!("Expected EnableCoinBalance::HD"), - }; - let account = balance.accounts.get(0).expect("Expected account at index 0"); - assert_eq!( - account.addresses[7].address, - "0xa420a4DBd8C50e6240014Db4587d2ec8D0cE0e6B" - ); - assert_eq!(account.addresses[0].balance.len(), 2); - assert!(account.addresses[0].balance.contains_key("ETH")); - assert!(account.addresses[0].balance.contains_key("ERC20DEV")); - - block_on(mm_hd.stop()).unwrap(); -} - fn request_and_check_orderbook_depth(mm_alice: &MarketMakerIt) { let rc = block_on(mm_alice.rpc(&json! ({ "userpass": mm_alice.userpass, @@ -5520,3 +5396,78 @@ fn test_orderbook_depth() { block_on(mm_bob.stop()).unwrap(); block_on(mm_alice.stop()).unwrap(); } + +#[test] +fn test_approve_erc20() { + let privkey = random_secp256k1_secret(); + fill_eth_erc20_with_private_key(privkey); + + let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); + let mm = MarketMakerIt::start( + Mm2TestConf::seednode(&format!("0x{}", hex::encode(privkey)), &coins).conf, + DEFAULT_RPC_PASSWORD.to_string(), + None, + ) + .unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("Node log path: {}", mm.log_path.display()); + + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + let _eth_enable = block_on(enable_eth_coin( + &mm, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + None, + false, + )); + let _erc20_enable = block_on(enable_eth_coin( + &mm, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + None, + false, + )); + + let rc = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "method":"approve_token", + "mmrpc":"2.0", + "id": 0, + "params":{ + "coin": "ERC20DEV", + "spender": swap_contract, + "amount": BigDecimal::from_str("11.0").unwrap(), + } + }))) + .unwrap(); + assert!(rc.0.is_success(), "approve_token error: {}", rc.1); + let res = serde_json::from_str::(&rc.1).unwrap(); + assert!( + hex::decode(str_strip_0x!(res["result"].as_str().unwrap())).is_ok(), + "approve_token result incorrect" + ); + + let rc = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "method":"get_token_allowance", + "mmrpc":"2.0", + "id": 0, + "params":{ + "coin": "ERC20DEV", + "spender": swap_contract, + } + }))) + .unwrap(); + assert!(rc.0.is_success(), "get_token_allowance error: {}", rc.1); + let res = serde_json::from_str::(&rc.1).unwrap(); + assert_eq!( + BigDecimal::from_str(res["result"].as_str().unwrap()).unwrap(), + BigDecimal::from_str("11.0").unwrap(), + "get_token_allowance result incorrect" + ); + + block_on(mm.stop()).unwrap(); +} diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index 1109e9f159..abcdc2ce9a 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -1,64 +1,115 @@ use super::docker_tests_common::{random_secp256k1_secret, ERC1155_TEST_ABI, ERC721_TEST_ABI, GETH_ACCOUNT, - GETH_ERC1155_CONTRACT, GETH_ERC20_CONTRACT, GETH_ERC721_CONTRACT, - GETH_NFT_SWAP_CONTRACT, GETH_NONCE_LOCK, GETH_RPC_URL, GETH_SWAP_CONTRACT, - GETH_WATCHERS_SWAP_CONTRACT, GETH_WEB3, MM_CTX}; + GETH_ERC1155_CONTRACT, GETH_ERC20_CONTRACT, GETH_ERC721_CONTRACT, GETH_MAKER_SWAP_V2, + GETH_NFT_MAKER_SWAP_V2, GETH_NONCE_LOCK, GETH_RPC_URL, GETH_SWAP_CONTRACT, + GETH_TAKER_SWAP_V2, GETH_WATCHERS_SWAP_CONTRACT, GETH_WEB3, MM_CTX, MM_CTX1}; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +use super::docker_tests_common::{SEPOLIA_ERC20_CONTRACT, SEPOLIA_ETOMIC_MAKER_NFT_SWAP_V2, SEPOLIA_MAKER_SWAP_V2, + SEPOLIA_NONCE_LOCK, SEPOLIA_RPC_URL, SEPOLIA_TAKER_SWAP_V2, SEPOLIA_TESTS_LOCK, + SEPOLIA_WEB3}; +use crate::common::Future01CompatExt; use bitcrypto::{dhash160, sha256}; -use coins::eth::{checksum_address, eth_addr_to_hex, eth_coin_from_conf_and_request, EthCoin, ERC20_ABI}; +use coins::eth::gas_limit::ETH_MAX_TRADE_GAS; +use coins::eth::v2_activation::{eth_coin_from_conf_and_request_v2, EthActivationV2Request, EthNode}; +use coins::eth::{checksum_address, eth_addr_to_hex, eth_coin_from_conf_and_request, EthCoin, EthCoinType, + EthPrivKeyBuildPolicy, SignedEthTx, SwapV2Contracts, ERC20_ABI}; use coins::nft::nft_structs::{Chain, ContractType, NftInfo}; -use coins::{CoinProtocol, CoinWithDerivationMethod, ConfirmPaymentInput, DerivationMethod, Eip1559Ops, - FoundSwapTxSpend, MakerNftSwapOpsV2, MarketCoinOps, NftSwapInfo, ParseCoinAssocTypes, PrivKeyBuildPolicy, - RefundPaymentArgs, SearchForSwapTxSpendInput, SendNftMakerPaymentArgs, SendPaymentArgs, - SpendNftMakerPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, SwapTxTypeWithSecretHash, ToBytes, - Transaction, ValidateNftMakerPaymentArgs}; -use common::{block_on, now_sec}; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +use coins::{lp_coinfind, CoinsContext, DexFee, FundingTxSpend, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, + MakerCoinSwapOpsV2, MmCoinEnum, MmCoinStruct, RefundFundingSecretArgs, RefundMakerPaymentSecretArgs, + RefundMakerPaymentTimelockArgs, RefundTakerPaymentArgs, SendMakerPaymentArgs, SendTakerFundingArgs, + SpendMakerPaymentArgs, TakerCoinSwapOpsV2, TxPreimageWithSig, ValidateMakerPaymentArgs, + ValidateTakerFundingArgs}; +use coins::{CoinProtocol, CoinWithDerivationMethod, CommonSwapOpsV2, ConfirmPaymentInput, DerivationMethod, + Eip1559Ops, FoundSwapTxSpend, MakerNftSwapOpsV2, MarketCoinOps, NftSwapInfo, ParseCoinAssocTypes, + ParseNftAssocTypes, PrivKeyBuildPolicy, RefundNftMakerPaymentArgs, RefundPaymentArgs, + SearchForSwapTxSpendInput, SendNftMakerPaymentArgs, SendPaymentArgs, SpendNftMakerPaymentArgs, + SpendPaymentArgs, SwapOps, SwapTxFeePolicy, SwapTxTypeWithSecretHash, ToBytes, Transaction, + ValidateNftMakerPaymentArgs}; +use common::{block_on, block_on_f01, now_sec}; use crypto::Secp256k1Secret; use ethereum_types::U256; -use futures01::Future; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +use mm2_core::mm_ctx::MmArc; use mm2_number::{BigDecimal, BigUint}; -use mm2_test_helpers::for_tests::{erc20_dev_conf, eth_dev_conf, nft_dev_conf}; +use mm2_test_helpers::for_tests::{account_balance, disable_coin, enable_erc20_token_v2, enable_eth_with_tokens_v2, + erc20_dev_conf, eth_dev_conf, get_new_address, get_token_info, nft_dev_conf, + MarketMakerIt, Mm2TestConf}; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +use mm2_test_helpers::for_tests::{eth_sepolia_conf, sepolia_erc20_dev_conf}; +use mm2_test_helpers::structs::{Bip44Chain, EnableCoinBalanceMap, EthWithTokensActivationResult, HDAccountAddressId, + TokenInfo}; +use serde_json::Value as Json; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +use std::str::FromStr; use std::thread; use std::time::Duration; use web3::contract::{Contract, Options}; use web3::ethabi::Token; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +use web3::types::BlockNumber; use web3::types::{Address, TransactionRequest, H256}; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +const SEPOLIA_MAKER_PRIV: &str = "6e2f3a6223b928a05a3a3622b0c3f3573d03663b704a61a6eb73326de0487928"; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +const SEPOLIA_TAKER_PRIV: &str = "e0be82dca60ff7e4c6d6db339ac9e1ae249af081dba2110bddd281e711608f16"; +const NFT_ETH: &str = "NFT_ETH"; +const ETH: &str = "ETH"; + +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +const ERC20: &str = "ERC20DEV"; + /// # Safety /// /// GETH_ACCOUNT is set once during initialization before tests start pub fn geth_account() -> Address { unsafe { GETH_ACCOUNT } } - /// # Safety /// /// GETH_SWAP_CONTRACT is set once during initialization before tests start pub fn swap_contract() -> Address { unsafe { GETH_SWAP_CONTRACT } } - /// # Safety /// -/// GETH_NFT_SWAP_CONTRACT is set once during initialization before tests start -pub fn nft_swap_contract() -> Address { unsafe { GETH_NFT_SWAP_CONTRACT } } - +/// GETH_MAKER_SWAP_V2 is set once during initialization before tests start +pub fn maker_swap_v2() -> Address { unsafe { GETH_MAKER_SWAP_V2 } } +/// # Safety +/// +/// GETH_TAKER_SWAP_V2 is set once during initialization before tests start +pub fn taker_swap_v2() -> Address { unsafe { GETH_TAKER_SWAP_V2 } } +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub fn sepolia_taker_swap_v2() -> Address { unsafe { SEPOLIA_TAKER_SWAP_V2 } } +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub fn sepolia_maker_swap_v2() -> Address { unsafe { SEPOLIA_MAKER_SWAP_V2 } } +/// # Safety +/// +/// GETH_NFT_MAKER_SWAP_V2 is set once during initialization before tests start +pub fn geth_nft_maker_swap_v2() -> Address { unsafe { GETH_NFT_MAKER_SWAP_V2 } } /// # Safety /// /// GETH_WATCHERS_SWAP_CONTRACT is set once during initialization before tests start pub fn watchers_swap_contract() -> Address { unsafe { GETH_WATCHERS_SWAP_CONTRACT } } - /// # Safety /// /// GETH_ERC20_CONTRACT is set once during initialization before tests start pub fn erc20_contract() -> Address { unsafe { GETH_ERC20_CONTRACT } } - +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub fn sepolia_erc20_contract() -> Address { unsafe { SEPOLIA_ERC20_CONTRACT } } /// Return ERC20 dev token contract address in checksum format pub fn erc20_contract_checksum() -> String { checksum_address(&format!("{:02x}", erc20_contract())) } - +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub fn sepolia_erc20_contract_checksum() -> String { checksum_address(&format!("{:02x}", sepolia_erc20_contract())) } /// # Safety /// /// GETH_ERC721_CONTRACT is set once during initialization before tests start -pub fn erc721_contract() -> Address { unsafe { GETH_ERC721_CONTRACT } } - +pub fn geth_erc721_contract() -> Address { unsafe { GETH_ERC721_CONTRACT } } /// # Safety /// /// GETH_ERC1155_CONTRACT is set once during initialization before tests start -pub fn erc1155_contract() -> Address { unsafe { GETH_ERC1155_CONTRACT } } +pub fn geth_erc1155_contract() -> Address { unsafe { GETH_ERC1155_CONTRACT } } +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +/// # Safety +/// +/// SEPOLIA_ETOMIC_MAKER_NFT_SWAP_V2 address is set once during initialization before tests start +pub fn sepolia_etomic_maker_nft() -> Address { unsafe { SEPOLIA_ETOMIC_MAKER_NFT_SWAP_V2 } } fn wait_for_confirmation(tx_hash: H256) { thread::sleep(Duration::from_millis(2000)); @@ -109,12 +160,13 @@ fn fill_erc20(to_addr: Address, amount: U256) { wait_for_confirmation(tx_hash); } -pub(crate) fn mint_erc721(to_addr: Address, token_id: U256) { +fn mint_erc721(to_addr: Address, token_id: U256) { let _guard = GETH_NONCE_LOCK.lock().unwrap(); - let erc721_contract = Contract::from_json(GETH_WEB3.eth(), erc721_contract(), ERC721_TEST_ABI.as_bytes()).unwrap(); + let erc721_contract = + Contract::from_json(GETH_WEB3.eth(), geth_erc721_contract(), ERC721_TEST_ABI.as_bytes()).unwrap(); let options = Options { - gas: Some(U256::from(150_000)), + gas: Some(U256::from(ETH_MAX_TRADE_GAS)), ..Options::default() }; @@ -137,23 +189,24 @@ pub(crate) fn mint_erc721(to_addr: Address, token_id: U256) { ); } -fn erc712_owner(token_id: U256) -> Address { +fn geth_erc712_owner(token_id: U256) -> Address { let _guard = GETH_NONCE_LOCK.lock().unwrap(); - let erc721_contract = Contract::from_json(GETH_WEB3.eth(), erc721_contract(), ERC721_TEST_ABI.as_bytes()).unwrap(); + let erc721_contract = + Contract::from_json(GETH_WEB3.eth(), geth_erc721_contract(), ERC721_TEST_ABI.as_bytes()).unwrap(); block_on(erc721_contract.query("ownerOf", Token::Uint(token_id), None, Options::default(), None)).unwrap() } -pub(crate) fn mint_erc1155(to_addr: Address, token_id: U256, amount: U256) { +fn mint_erc1155(to_addr: Address, token_id: U256, amount: u32) { let _guard = GETH_NONCE_LOCK.lock().unwrap(); let erc1155_contract = - Contract::from_json(GETH_WEB3.eth(), erc1155_contract(), ERC1155_TEST_ABI.as_bytes()).unwrap(); + Contract::from_json(GETH_WEB3.eth(), geth_erc1155_contract(), ERC1155_TEST_ABI.as_bytes()).unwrap(); let tx_hash = block_on(erc1155_contract.call( "mint", ( Token::Address(to_addr), Token::Uint(token_id), - Token::Uint(amount), + Token::Uint(U256::from(amount)), Token::Bytes("".into()), ), geth_account(), @@ -172,17 +225,22 @@ pub(crate) fn mint_erc1155(to_addr: Address, token_id: U256, amount: U256) { )) .unwrap(); + // check that "balanceOf" from ERC11155 returns the exact amount of token without any decimals or scaling factors + let balance_dec = balance.to_string().parse::().unwrap(); assert_eq!( - balance, amount, + balance_dec, + BigDecimal::from(amount), "The balance of tokenId {:?} for address {:?} does not match the expected amount {:?}.", - token_id, to_addr, amount + token_id, + to_addr, + amount ); } -fn erc1155_balance(wallet_addr: Address, token_id: U256) -> U256 { +fn geth_erc1155_balance(wallet_addr: Address, token_id: U256) -> U256 { let _guard = GETH_NONCE_LOCK.lock().unwrap(); let erc1155_contract = - Contract::from_json(GETH_WEB3.eth(), erc1155_contract(), ERC1155_TEST_ABI.as_bytes()).unwrap(); + Contract::from_json(GETH_WEB3.eth(), geth_erc1155_contract(), ERC1155_TEST_ABI.as_bytes()).unwrap(); block_on(erc1155_contract.query( "balanceOf", (Token::Address(wallet_addr), Token::Uint(token_id)), @@ -193,35 +251,35 @@ fn erc1155_balance(wallet_addr: Address, token_id: U256) -> U256 { .unwrap() } -pub(crate) async fn fill_erc1155_info(eth_coin: &EthCoin, tokens_id: u32, amount: u32) { +pub(crate) async fn fill_erc1155_info(eth_coin: &EthCoin, token_address: Address, token_id: u32, amount: u32) { let nft_infos_lock = eth_coin.nfts_infos.clone(); let mut nft_infos = nft_infos_lock.lock().await; let erc1155_nft_info = NftInfo { - token_address: erc1155_contract(), - token_id: BigUint::from(tokens_id), + token_address, + token_id: BigUint::from(token_id), chain: Chain::Eth, contract_type: ContractType::Erc1155, amount: BigDecimal::from(amount), }; - let erc1155_address_str = eth_addr_to_hex(&erc1155_contract()); - let erc1155_key = format!("{},{}", erc1155_address_str, tokens_id); + let erc1155_address_str = eth_addr_to_hex(&token_address); + let erc1155_key = format!("{},{}", erc1155_address_str, token_id); nft_infos.insert(erc1155_key, erc1155_nft_info); } -pub(crate) async fn fill_erc721_info(eth_coin: &EthCoin, tokens_id: u32) { +pub(crate) async fn fill_erc721_info(eth_coin: &EthCoin, token_address: Address, token_id: u32) { let nft_infos_lock = eth_coin.nfts_infos.clone(); let mut nft_infos = nft_infos_lock.lock().await; let erc721_nft_info = NftInfo { - token_address: erc721_contract(), - token_id: BigUint::from(tokens_id), + token_address, + token_id: BigUint::from(token_id), chain: Chain::Eth, contract_type: ContractType::Erc721, amount: BigDecimal::from(1), }; - let erc721_address_str = eth_addr_to_hex(&erc721_contract()); - let erc721_key = format!("{},{}", erc721_address_str, tokens_id); + let erc721_address_str = eth_addr_to_hex(&token_address); + let erc721_key = format!("{},{}", erc721_address_str, token_id); nft_infos.insert(erc721_key, erc721_nft_info); } @@ -298,6 +356,7 @@ pub fn erc20_coin_with_random_privkey(swap_contract_address: Address) -> EthCoin erc20_coin } +#[derive(Clone, Copy, Debug)] pub enum TestNftType { Erc1155 { token_id: u32, amount: u32 }, Erc721 { token_id: u32 }, @@ -306,46 +365,141 @@ pub enum TestNftType { /// Generates a global NFT coin instance with a random private key and an initial 100 ETH balance. /// Optionally mints a specified NFT (either ERC721 or ERC1155) to the global NFT address, /// with details recorded in the `nfts_infos` field based on the provided `nft_type`. -pub fn global_nft_with_random_privkey(swap_contract_address: Address, nft_type: Option) -> EthCoin { - let nft_conf = nft_dev_conf(); - let req = json!({ - "method": "enable", - "coin": "NFT_ETH", - "urls": [GETH_RPC_URL], - "swap_contract_address": swap_contract_address, - }); - - let global_nft = block_on(eth_coin_from_conf_and_request( - &MM_CTX, - "NFT_ETH", - &nft_conf, - &req, - CoinProtocol::NFT { - platform: "ETH".to_string(), - }, - PrivKeyBuildPolicy::IguanaPrivKey(random_secp256k1_secret()), +fn global_nft_with_random_privkey( + swap_v2_contracts: SwapV2Contracts, + swap_contract_address: Address, + fallback_swap_contract_address: Address, + nft_type: Option, + nft_ticker: String, + platform_ticker: String, +) -> EthCoin { + let build_policy = EthPrivKeyBuildPolicy::IguanaPrivKey(random_secp256k1_secret()); + let node = EthNode { + url: GETH_RPC_URL.to_string(), + komodo_proxy: false, + }; + let platform_request = EthActivationV2Request { + nodes: vec![node], + rpc_mode: Default::default(), + swap_contract_address, + swap_v2_contracts: Some(swap_v2_contracts), + fallback_swap_contract: Some(fallback_swap_contract_address), + contract_supports_watchers: false, + mm2: None, + required_confirmations: None, + priv_key_policy: Default::default(), + enable_params: Default::default(), + path_to_address: Default::default(), + gap_limit: None, + }; + let coin = block_on(eth_coin_from_conf_and_request_v2( + &MM_CTX1, + nft_ticker.as_str(), + &nft_dev_conf(), + platform_request, + build_policy, )) .unwrap(); - let my_address = block_on(global_nft.my_addr()); + let coin_type = EthCoinType::Nft { + platform: platform_ticker, + }; + let global_nft = block_on(coin.set_coin_type(coin_type)); + let my_address = block_on(coin.my_addr()); fill_eth(my_address, U256::from(10).pow(U256::from(20))); if let Some(nft_type) = nft_type { match nft_type { TestNftType::Erc1155 { token_id, amount } => { - mint_erc1155(my_address, U256::from(token_id), U256::from(amount)); - block_on(fill_erc1155_info(&global_nft, token_id, amount)); + mint_erc1155(my_address, U256::from(token_id), amount); + block_on(fill_erc1155_info( + &global_nft, + geth_erc1155_contract(), + token_id, + amount, + )); }, TestNftType::Erc721 { token_id } => { mint_erc721(my_address, U256::from(token_id)); - block_on(fill_erc721_info(&global_nft, token_id)); + block_on(fill_erc721_info(&global_nft, geth_erc721_contract(), token_id)); }, } } - global_nft } +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +/// Can be used to generate coin from Sepolia Maker/Taker priv keys. +fn sepolia_coin_from_privkey(ctx: &MmArc, secret: &'static str, ticker: &str, conf: &Json, erc20: bool) -> EthCoin { + let swap_addr = SwapAddresses { + swap_v2_contracts: SwapV2Contracts { + maker_swap_v2_contract: sepolia_maker_swap_v2(), + taker_swap_v2_contract: sepolia_taker_swap_v2(), + nft_maker_swap_v2_contract: sepolia_etomic_maker_nft(), + }, + swap_contract_address: sepolia_taker_swap_v2(), + fallback_swap_contract_address: sepolia_taker_swap_v2(), + }; + + let priv_key = Secp256k1Secret::from(secret); + let build_policy = EthPrivKeyBuildPolicy::IguanaPrivKey(priv_key); + + let node = EthNode { + url: SEPOLIA_RPC_URL.to_string(), + komodo_proxy: false, + }; + let platform_request = EthActivationV2Request { + nodes: vec![node], + rpc_mode: Default::default(), + swap_contract_address: swap_addr.swap_contract_address, + swap_v2_contracts: Some(swap_addr.swap_v2_contracts), + fallback_swap_contract: Some(swap_addr.fallback_swap_contract_address), + contract_supports_watchers: false, + mm2: None, + required_confirmations: None, + priv_key_policy: Default::default(), + enable_params: Default::default(), + path_to_address: Default::default(), + gap_limit: None, + }; + let coin = block_on(eth_coin_from_conf_and_request_v2( + ctx, + ticker, + conf, + platform_request, + build_policy, + )) + .unwrap(); + let coin = if erc20 { + let coin_type = EthCoinType::Erc20 { + platform: ETH.to_string(), + token_addr: sepolia_erc20_contract(), + }; + block_on(coin.set_coin_type(coin_type)) + } else { + coin + }; + + let coins_ctx = CoinsContext::from_ctx(ctx).unwrap(); + let mut coins = block_on(coins_ctx.lock_coins()); + coins.insert( + coin.ticker().into(), + MmCoinStruct::new(MmCoinEnum::EthCoin(coin.clone())), + ); + coin +} + +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +fn get_or_create_sepolia_coin(ctx: &MmArc, priv_key: &'static str, ticker: &str, conf: &Json, erc20: bool) -> EthCoin { + match block_on(lp_coinfind(ctx, ticker)).unwrap() { + None => sepolia_coin_from_privkey(ctx, priv_key, ticker, conf, erc20), + Some(mm_coin) => match mm_coin { + MmCoinEnum::EthCoin(coin) => coin, + _ => panic!("Unexpected coin type found. Expected MmCoinEnum::EthCoin"), + }, + } +} + /// Fills the private key's public address with ETH and ERC20 tokens pub fn fill_eth_erc20_with_private_key(priv_key: Secp256k1Secret) { let eth_conf = eth_dev_conf(); @@ -395,6 +549,7 @@ pub fn fill_eth_erc20_with_private_key(priv_key: Secp256k1Secret) { } fn send_and_refund_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { + thread::sleep(Duration::from_secs(3)); let eth_coin = eth_coin_with_random_privkey(swap_contract()); eth_coin.set_swap_transaction_fee_policy(swap_txfee_policy); @@ -416,7 +571,7 @@ fn send_and_refund_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { watcher_reward: None, wait_for_confirmation_until: 0, }; - let eth_maker_payment = eth_coin.send_maker_payment(send_payment_args).wait().unwrap(); + let eth_maker_payment = block_on(eth_coin.send_maker_payment(send_payment_args)).unwrap(); let confirm_input = ConfirmPaymentInput { payment_tx: eth_maker_payment.tx_hex(), @@ -425,7 +580,7 @@ fn send_and_refund_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { wait_until: now_sec() + 60, check_every: 1, }; - eth_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(eth_coin.wait_for_confirmations(confirm_input)).unwrap(); let refund_args = RefundPaymentArgs { payment_tx: ð_maker_payment.tx_hex(), @@ -448,7 +603,7 @@ fn send_and_refund_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { wait_until: now_sec() + 60, check_every: 1, }; - eth_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(eth_coin.wait_for_confirmations(confirm_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -502,7 +657,7 @@ fn send_and_spend_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { watcher_reward: None, wait_for_confirmation_until: 0, }; - let eth_maker_payment = maker_eth_coin.send_maker_payment(send_payment_args).wait().unwrap(); + let eth_maker_payment = block_on(maker_eth_coin.send_maker_payment(send_payment_args)).unwrap(); let confirm_input = ConfirmPaymentInput { payment_tx: eth_maker_payment.tx_hex(), @@ -511,7 +666,7 @@ fn send_and_spend_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { wait_until: now_sec() + 60, check_every: 1, }; - taker_eth_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(taker_eth_coin.wait_for_confirmations(confirm_input)).unwrap(); let spend_args = SpendPaymentArgs { other_payment_tx: ð_maker_payment.tx_hex(), @@ -533,7 +688,7 @@ fn send_and_spend_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { wait_until: now_sec() + 60, check_every: 1, }; - taker_eth_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(taker_eth_coin.wait_for_confirmations(confirm_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -562,6 +717,7 @@ fn send_and_spend_eth_maker_payment_internal_gas_policy() { fn send_and_spend_eth_maker_payment_priority_fee() { send_and_spend_eth_maker_payment_impl(SwapTxFeePolicy::Medium); } fn send_and_refund_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { + thread::sleep(Duration::from_secs(10)); let erc20_coin = erc20_coin_with_random_privkey(swap_contract()); erc20_coin.set_swap_transaction_fee_policy(swap_txfee_policy); @@ -584,7 +740,7 @@ fn send_and_refund_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) watcher_reward: None, wait_for_confirmation_until: now_sec() + 60, }; - let eth_maker_payment = erc20_coin.send_maker_payment(send_payment_args).wait().unwrap(); + let eth_maker_payment = block_on(erc20_coin.send_maker_payment(send_payment_args)).unwrap(); let confirm_input = ConfirmPaymentInput { payment_tx: eth_maker_payment.tx_hex(), @@ -593,7 +749,7 @@ fn send_and_refund_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) wait_until: now_sec() + 60, check_every: 1, }; - erc20_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(erc20_coin.wait_for_confirmations(confirm_input)).unwrap(); let refund_args = RefundPaymentArgs { payment_tx: ð_maker_payment.tx_hex(), @@ -616,7 +772,7 @@ fn send_and_refund_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) wait_until: now_sec() + 60, check_every: 1, }; - erc20_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(erc20_coin.wait_for_confirmations(confirm_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -647,6 +803,7 @@ fn send_and_refund_erc20_maker_payment_priority_fee() { } fn send_and_spend_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { + thread::sleep(Duration::from_secs(7)); let maker_erc20_coin = erc20_coin_with_random_privkey(swap_contract()); let taker_erc20_coin = erc20_coin_with_random_privkey(swap_contract()); @@ -672,7 +829,7 @@ fn send_and_spend_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { watcher_reward: None, wait_for_confirmation_until: now_sec() + 60, }; - let eth_maker_payment = maker_erc20_coin.send_maker_payment(send_payment_args).wait().unwrap(); + let eth_maker_payment = block_on(maker_erc20_coin.send_maker_payment(send_payment_args)).unwrap(); let confirm_input = ConfirmPaymentInput { payment_tx: eth_maker_payment.tx_hex(), @@ -681,7 +838,7 @@ fn send_and_spend_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { wait_until: now_sec() + 60, check_every: 1, }; - taker_erc20_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(taker_erc20_coin.wait_for_confirmations(confirm_input)).unwrap(); let spend_args = SpendPaymentArgs { other_payment_tx: ð_maker_payment.tx_hex(), @@ -703,7 +860,7 @@ fn send_and_spend_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { wait_until: now_sec() + 60, check_every: 1, }; - taker_erc20_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(taker_erc20_coin.wait_for_confirmations(confirm_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -733,207 +890,1853 @@ fn send_and_spend_erc20_maker_payment_priority_fee() { send_and_spend_erc20_maker_payment_impl(SwapTxFeePolicy::Medium); } +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +/// Wait for all pending transactions for the given address to be confirmed +fn wait_pending_transactions(wallet_address: Address) { + let _guard = SEPOLIA_NONCE_LOCK.lock().unwrap(); + let web3 = SEPOLIA_WEB3.clone(); + + loop { + let latest_nonce = block_on(web3.eth().transaction_count(wallet_address, Some(BlockNumber::Latest))).unwrap(); + let pending_nonce = block_on(web3.eth().transaction_count(wallet_address, Some(BlockNumber::Pending))).unwrap(); + + if latest_nonce == pending_nonce { + log!( + "All pending transactions have been confirmed. Latest nonce: {}", + latest_nonce + ); + break; + } else { + log!( + "Waiting for pending transactions to confirm... Current nonce: {}, Pending nonce: {}", + latest_nonce, + pending_nonce + ); + thread::sleep(Duration::from_secs(1)); + } + } +} + #[test] fn send_and_spend_erc721_maker_payment() { - // TODO: Evaluate implementation strategy — either employing separate contracts for maker and taker - // functionalities for both coins and NFTs, or utilizing the Diamond Standard (EIP-2535) for a unified contract approach. - // Decision will inform whether to maintain multiple "swap_contract_address" fields in `EthCoin` for distinct contract types - // or a singular field for a Diamond Standard-compatible contract address. + let token_id = 1u32; + let time_lock = now_sec() + 1000; + let activation = NftActivationV2Args::init(); + let setup = setup_test( + token_id, + None, + ContractType::Erc721, + geth_erc721_contract(), + time_lock, + activation, + ); + + let maker_payment = send_nft_maker_payment(&setup, 1.into()); + log!( + "Maker sent ERC721 NFT payment, tx hash: {:02x}", + maker_payment.tx_hash() + ); - let erc721_nft = TestNftType::Erc721 { token_id: 2 }; + wait_for_confirmations(&setup.maker_global_nft, &maker_payment, 200); + validate_nft_maker_payment(&setup, &maker_payment, 1.into()); - let maker_global_nft = global_nft_with_random_privkey(nft_swap_contract(), Some(erc721_nft)); - let taker_global_nft = global_nft_with_random_privkey(nft_swap_contract(), None); + let spend_tx = spend_nft_maker_payment(&setup, &maker_payment, &ContractType::Erc721); + log!( + "Taker spent ERC721 NFT Maker payment, tx hash: {:02x}", + spend_tx.tx_hash() + ); + + wait_for_confirmations(&setup.taker_global_nft, &spend_tx, 200); + let new_owner = geth_erc712_owner(U256::from(token_id)); + let taker_address = block_on(setup.taker_global_nft.my_addr()); + assert_eq!(new_owner, taker_address); +} +#[test] +fn send_and_spend_erc1155_maker_payment() { + let token_id = 1u32; + let amount = 3u32; let time_lock = now_sec() + 1000; - let maker_pubkey = maker_global_nft.derive_htlc_pubkey(&[]); - let taker_pubkey = taker_global_nft.derive_htlc_pubkey(&[]); + let activation = NftActivationV2Args::init(); + let setup = setup_test( + token_id, + Some(amount), + ContractType::Erc1155, + geth_erc1155_contract(), + time_lock, + activation, + ); - let maker_secret = &[1; 32]; - let maker_secret_hash = sha256(maker_secret).to_vec(); + let maker_address = block_on(setup.maker_global_nft.my_addr()); + let maker_balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(amount), maker_balance); - let nft_swap_info = NftSwapInfo { - token_address: &erc721_contract(), - token_id: &BigUint::from(2u32).to_bytes(), - contract_type: &ContractType::Erc721, - swap_contract_address: &nft_swap_contract(), - }; + let swap_amount = 2u32; + let maker_payment = send_nft_maker_payment(&setup, swap_amount.into()); + log!( + "Maker sent ERC1155 NFT payment, tx hash: {:02x}", + maker_payment.tx_hash() + ); - let send_payment_args: SendNftMakerPaymentArgs = SendNftMakerPaymentArgs { - time_lock, - taker_secret_hash: &[0; 32], - maker_secret_hash: &maker_secret_hash, - amount: 1.into(), - taker_pub: &taker_global_nft.parse_pubkey(&taker_pubkey).unwrap(), - swap_unique_data: &[], - nft_swap_info: &nft_swap_info, - }; - let maker_payment = block_on(maker_global_nft.send_nft_maker_payment_v2(send_payment_args)).unwrap(); + wait_for_confirmations(&setup.maker_global_nft, &maker_payment, 100); + + validate_nft_maker_payment(&setup, &maker_payment, swap_amount.into()); + + let spend_tx = spend_nft_maker_payment(&setup, &maker_payment, &ContractType::Erc1155); log!( - "Maker sent ERC721 NFT Payment tx hash {:02x}", - maker_payment.tx_hash_as_bytes() + "Taker spent ERC1155 NFT Maker payment, tx hash: {:02x}", + spend_tx.tx_hash() ); - let confirm_input = ConfirmPaymentInput { - payment_tx: maker_payment.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: now_sec() + 70, - check_every: 1, - }; - maker_global_nft.wait_for_confirmations(confirm_input).wait().unwrap(); + wait_for_confirmations(&setup.taker_global_nft, &spend_tx, 100); - let validate_args = ValidateNftMakerPaymentArgs { - maker_payment_tx: &maker_payment, - time_lock, - taker_secret_hash: &[0; 32], - maker_secret_hash: &maker_secret_hash, - amount: 1.into(), - taker_pub: &taker_global_nft.parse_pubkey(&taker_pubkey).unwrap(), - maker_pub: &maker_global_nft.parse_pubkey(&maker_pubkey).unwrap(), - swap_unique_data: &[], - nft_swap_info: &nft_swap_info, - }; - block_on(maker_global_nft.validate_nft_maker_payment_v2(validate_args)).unwrap(); + let taker_address = block_on(setup.taker_global_nft.my_addr()); + let taker_balance = geth_erc1155_balance(taker_address, U256::from(token_id)); + assert_eq!(U256::from(swap_amount), taker_balance); - let spend_payment_args = SpendNftMakerPaymentArgs { - maker_payment_tx: &maker_payment, - time_lock, - taker_secret_hash: &[0; 32], - maker_secret_hash: &maker_secret_hash, - maker_secret, - maker_pub: &maker_global_nft.parse_pubkey(&maker_pubkey).unwrap(), - swap_unique_data: &[], - contract_type: &ContractType::Erc721, - swap_contract_address: &nft_swap_contract(), - }; - let spend_tx = block_on(taker_global_nft.spend_nft_maker_payment_v2(spend_payment_args)).unwrap(); + let maker_new_balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(1u32), maker_new_balance); +} - let confirm_input = ConfirmPaymentInput { - payment_tx: spend_tx.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: now_sec() + 70, - check_every: 1, - }; - taker_global_nft.wait_for_confirmations(confirm_input).wait().unwrap(); +#[test] +fn test_nonce_several_urls() { + // Use one working and one failing URL. + let coin = eth_coin_with_random_privkey_using_urls(swap_contract(), &[GETH_RPC_URL, "http://127.0.0.1:0"]); + let my_address = block_on(coin.derivation_method().single_addr_or_err()).unwrap(); + let (old_nonce, _) = block_on_f01(coin.clone().get_addr_nonce(my_address)).unwrap(); + + // Send a payment to increase the nonce. + block_on_f01(coin.send_to_address(my_address, 200000000.into())).unwrap(); - let new_owner = erc712_owner(U256::from(2)); - let my_address = block_on(taker_global_nft.my_addr()); - assert_eq!(new_owner, my_address); + let (new_nonce, _) = block_on_f01(coin.get_addr_nonce(my_address)).unwrap(); + assert_eq!(old_nonce + 1, new_nonce); } #[test] -fn send_and_spend_erc1155_maker_payment() { - let erc1155_nft = TestNftType::Erc1155 { token_id: 4, amount: 3 }; +fn test_nonce_lock() { + use futures::future::join_all; - let maker_global_nft = global_nft_with_random_privkey(nft_swap_contract(), Some(erc1155_nft)); - let taker_global_nft = global_nft_with_random_privkey(nft_swap_contract(), None); + let coin = eth_coin_with_random_privkey(swap_contract()); + let my_address = block_on(coin.derivation_method().single_addr_or_err()).unwrap(); + let futures = (0..5).map(|_| coin.send_to_address(my_address, 200000000.into()).compat()); + let results = block_on(join_all(futures)); - let time_lock = now_sec() + 1000; - let maker_pubkey = maker_global_nft.derive_htlc_pubkey(&[]); - let taker_pubkey = taker_global_nft.derive_htlc_pubkey(&[]); + // make sure all transactions are successful + for result in results { + result.unwrap(); + } +} + +#[test] +fn send_and_refund_erc721_maker_payment_timelock() { + let token_id = 2u32; + let time_lock_to_refund = now_sec() - 1000; + let activation = NftActivationV2Args::init(); + let setup = setup_test( + token_id, + None, + ContractType::Erc721, + geth_erc721_contract(), + time_lock_to_refund, + activation, + ); - let maker_secret = &[1; 32]; - let maker_secret_hash = sha256(maker_secret).to_vec(); + let maker_payment_to_refund = send_nft_maker_payment(&setup, 1.into()); + log!( + "Maker sent ERC721 NFT payment, tx hash: {:02x}", + maker_payment_to_refund.tx_hash() + ); - let nft_swap_info = NftSwapInfo { - token_address: &erc1155_contract(), - token_id: &BigUint::from(4u32).to_bytes(), - contract_type: &ContractType::Erc1155, - swap_contract_address: &nft_swap_contract(), + wait_for_confirmations(&setup.maker_global_nft, &maker_payment_to_refund, 150); + let current_owner = geth_erc712_owner(U256::from(token_id)); + assert_eq!(current_owner, geth_nft_maker_swap_v2()); + + let refund_timelock_tx = refund_nft_maker_payment( + &setup, + &maker_payment_to_refund, + &ContractType::Erc721, + RefundType::Timelock, + ); + log!( + "Maker refunded ERC721 NFT payment after timelock, tx hash: {:02x}", + refund_timelock_tx.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &refund_timelock_tx, 150); + let current_owner = geth_erc712_owner(U256::from(token_id)); + let maker_address = block_on(setup.maker_global_nft.my_addr()); + assert_eq!(current_owner, maker_address); +} + +#[test] +fn send_and_refund_erc1155_maker_payment_timelock() { + let token_id = 2u32; + let amount = 3u32; + let time_lock_to_refund = now_sec() - 1000; + let activation = NftActivationV2Args::init(); + let setup = setup_test( + token_id, + Some(amount), + ContractType::Erc1155, + geth_erc1155_contract(), + time_lock_to_refund, + activation, + ); + + let maker_address = block_on(setup.maker_global_nft.my_addr()); + let balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(amount), balance); + + let swap_amount = 2u32; + let maker_payment_to_refund = send_nft_maker_payment(&setup, swap_amount.into()); + log!( + "Maker sent ERC1155 NFT payment, tx hash: {:02x}", + maker_payment_to_refund.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &maker_payment_to_refund, 150); + + let swap_contract_balance = geth_erc1155_balance(geth_nft_maker_swap_v2(), U256::from(token_id)); + assert_eq!(U256::from(swap_amount), swap_contract_balance); + let balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(1u32), balance); + + let refund_timelock_tx = refund_nft_maker_payment( + &setup, + &maker_payment_to_refund, + &ContractType::Erc1155, + RefundType::Timelock, + ); + log!( + "Maker refunded ERC1155 NFT payment after timelock, tx hash: {:02x}", + refund_timelock_tx.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &refund_timelock_tx, 150); + + let balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(amount), balance); +} + +#[test] +fn send_and_refund_erc721_maker_payment_secret() { + let token_id = 3u32; + let time_lock_to_refund = now_sec() + 1000; + let activation = NftActivationV2Args::init(); + let setup = setup_test( + token_id, + None, + ContractType::Erc721, + geth_erc721_contract(), + time_lock_to_refund, + activation, + ); + + let maker_payment_to_refund = send_nft_maker_payment(&setup, 1.into()); + log!( + "Maker sent ERC721 NFT payment, tx hash: {:02x}", + maker_payment_to_refund.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &maker_payment_to_refund, 150); + let current_owner = geth_erc712_owner(U256::from(token_id)); + assert_eq!(current_owner, geth_nft_maker_swap_v2()); + + let refund_secret_tx = refund_nft_maker_payment( + &setup, + &maker_payment_to_refund, + &ContractType::Erc721, + RefundType::Secret, + ); + log!( + "Maker refunded ERC721 NFT payment using Taker secret, tx hash: {:02x}", + refund_secret_tx.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &refund_secret_tx, 150); + let current_owner = geth_erc712_owner(U256::from(token_id)); + let maker_address = block_on(setup.maker_global_nft.my_addr()); + assert_eq!(current_owner, maker_address); +} + +#[test] +fn send_and_refund_erc1155_maker_payment_secret() { + let token_id = 3u32; + let amount = 3u32; + let time_lock_to_refund = now_sec() + 1000; + let activation = NftActivationV2Args::init(); + let setup = setup_test( + token_id, + Some(amount), + ContractType::Erc1155, + geth_erc1155_contract(), + time_lock_to_refund, + activation, + ); + + let maker_address = block_on(setup.maker_global_nft.my_addr()); + let balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(amount), balance); + + let swap_amount = 2u32; + let maker_payment_to_refund = send_nft_maker_payment(&setup, swap_amount.into()); + log!( + "Maker sent ERC1155 NFT payment, tx hash: {:02x}", + maker_payment_to_refund.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &maker_payment_to_refund, 100); + + let swap_contract_balance = geth_erc1155_balance(geth_nft_maker_swap_v2(), U256::from(token_id)); + assert_eq!(U256::from(swap_amount), swap_contract_balance); + let balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(1u32), balance); + + let refund_secret_tx = refund_nft_maker_payment( + &setup, + &maker_payment_to_refund, + &ContractType::Erc1155, + RefundType::Secret, + ); + log!( + "Maker refunded ERC1155 NFT payment using Taker secret, tx hash: {:02x}", + refund_secret_tx.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &refund_secret_tx, 100); + + let balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(amount), balance); +} + +struct NftTestSetup { + maker_global_nft: EthCoin, + taker_global_nft: EthCoin, + nft_swap_info: TestNftSwapInfo, + maker_secret: Vec, + maker_secret_hash: Vec, + taker_secret: Vec, + taker_secret_hash: Vec, + time_lock: u64, +} + +/// Structure representing necessary NFT info for Swap +pub struct TestNftSwapInfo { + /// The address of the NFT token + pub token_address: Coin::ContractAddress, + /// The ID of the NFT token. + pub token_id: Vec, + /// The type of smart contract that governs this NFT + pub contract_type: Coin::ContractType, +} + +struct NftActivationV2Args { + swap_contract_address: Address, + fallback_swap_contract_address: Address, + swap_v2_contracts: SwapV2Contracts, + nft_ticker: String, + platform_ticker: String, +} + +impl NftActivationV2Args { + fn init() -> Self { + Self { + swap_contract_address: swap_contract(), + fallback_swap_contract_address: swap_contract(), + swap_v2_contracts: SwapV2Contracts { + maker_swap_v2_contract: maker_swap_v2(), + taker_swap_v2_contract: taker_swap_v2(), + nft_maker_swap_v2_contract: geth_nft_maker_swap_v2(), + }, + nft_ticker: NFT_ETH.to_string(), + platform_ticker: "ETH".to_string(), + } + } +} + +fn setup_test( + token_id: u32, + amount: Option, + contract_type: ContractType, + token_contract: Address, + time_lock: u64, + activation: NftActivationV2Args, +) -> NftTestSetup { + let nft_type = match contract_type { + ContractType::Erc721 => TestNftType::Erc721 { token_id }, + ContractType::Erc1155 => TestNftType::Erc1155 { + token_id, + amount: amount.unwrap(), + }, + }; + + let maker_global_nft = global_nft_with_random_privkey( + activation.swap_v2_contracts, + activation.swap_contract_address, + activation.fallback_swap_contract_address, + Some(nft_type), + activation.nft_ticker.clone(), + activation.platform_ticker.clone(), + ); + let taker_global_nft = global_nft_with_random_privkey( + activation.swap_v2_contracts, + activation.swap_contract_address, + activation.fallback_swap_contract_address, + None, + activation.nft_ticker, + activation.platform_ticker, + ); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + + let token_id = BigUint::from(token_id).to_bytes(); + + let nft_swap_info = TestNftSwapInfo { + token_address: token_contract, + token_id, + contract_type, }; - let send_payment_args: SendNftMakerPaymentArgs = SendNftMakerPaymentArgs { + NftTestSetup { + maker_global_nft, + taker_global_nft, + nft_swap_info, + maker_secret, + maker_secret_hash, + taker_secret, + taker_secret_hash, time_lock, - taker_secret_hash: &[0; 32], - maker_secret_hash: &maker_secret_hash, - amount: 3.into(), - taker_pub: &taker_global_nft.parse_pubkey(&taker_pubkey).unwrap(), + } +} + +fn send_nft_maker_payment(setup: &NftTestSetup, amount: BigDecimal) -> SignedEthTx { + let nft_swap_info = NftSwapInfo { + token_address: &setup.nft_swap_info.token_address, + token_id: &setup.nft_swap_info.token_id, + contract_type: &setup.nft_swap_info.contract_type, + }; + let send_payment_args = SendNftMakerPaymentArgs:: { + time_lock: setup.time_lock, + taker_secret_hash: &setup.taker_secret_hash, + maker_secret_hash: &setup.maker_secret_hash, + amount, + taker_pub: &setup.taker_global_nft.derive_htlc_pubkey_v2(&[]), swap_unique_data: &[], nft_swap_info: &nft_swap_info, }; - let maker_payment = block_on(maker_global_nft.send_nft_maker_payment_v2(send_payment_args)).unwrap(); - log!( - "Maker sent ERC1155 NFT Payment tx hash {:02x}", - maker_payment.tx_hash_as_bytes() - ); + block_on(setup.maker_global_nft.send_nft_maker_payment_v2(send_payment_args)).unwrap() +} +fn wait_for_confirmations(coin: &EthCoin, tx: &SignedEthTx, wait_seconds: u64) { let confirm_input = ConfirmPaymentInput { - payment_tx: maker_payment.tx_hex(), + payment_tx: tx.tx_hex(), confirmations: 1, requires_nota: false, - wait_until: now_sec() + 60, + wait_until: now_sec() + wait_seconds, check_every: 1, }; - maker_global_nft.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_input)).unwrap(); +} +fn validate_nft_maker_payment(setup: &NftTestSetup, maker_payment: &SignedEthTx, amount: BigDecimal) { + let nft_swap_info = NftSwapInfo { + token_address: &setup.nft_swap_info.token_address, + token_id: &setup.nft_swap_info.token_id, + contract_type: &setup.nft_swap_info.contract_type, + }; let validate_args = ValidateNftMakerPaymentArgs { - maker_payment_tx: &maker_payment, - time_lock, - taker_secret_hash: &[0; 32], - maker_secret_hash: &maker_secret_hash, - amount: 3.into(), - taker_pub: &taker_global_nft.parse_pubkey(&taker_pubkey).unwrap(), - maker_pub: &maker_global_nft.parse_pubkey(&maker_pubkey).unwrap(), + maker_payment_tx: maker_payment, + time_lock: setup.time_lock, + taker_secret_hash: &setup.taker_secret_hash, + maker_secret_hash: &setup.maker_secret_hash, + amount, + taker_pub: &setup.taker_global_nft.derive_htlc_pubkey_v2(&[]), + maker_pub: &setup.maker_global_nft.derive_htlc_pubkey_v2(&[]), swap_unique_data: &[], nft_swap_info: &nft_swap_info, }; - block_on(maker_global_nft.validate_nft_maker_payment_v2(validate_args)).unwrap(); + block_on(setup.maker_global_nft.validate_nft_maker_payment_v2(validate_args)).unwrap() +} +fn spend_nft_maker_payment( + setup: &NftTestSetup, + maker_payment: &SignedEthTx, + contract_type: &ContractType, +) -> SignedEthTx { let spend_payment_args = SpendNftMakerPaymentArgs { - maker_payment_tx: &maker_payment, - time_lock, - taker_secret_hash: &[0; 32], - maker_secret_hash: &maker_secret_hash, - maker_secret, - maker_pub: &maker_global_nft.parse_pubkey(&maker_pubkey).unwrap(), + maker_payment_tx: maker_payment, + taker_secret_hash: &setup.taker_secret_hash, + maker_secret_hash: &setup.maker_secret_hash, + maker_secret: &setup.maker_secret, + maker_pub: &setup.maker_global_nft.derive_htlc_pubkey_v2(&[]), swap_unique_data: &[], - contract_type: &ContractType::Erc1155, - swap_contract_address: &nft_swap_contract(), + contract_type, }; - let spend_tx = block_on(taker_global_nft.spend_nft_maker_payment_v2(spend_payment_args)).unwrap(); + block_on(setup.taker_global_nft.spend_nft_maker_payment_v2(spend_payment_args)).unwrap() +} - let confirm_input = ConfirmPaymentInput { - payment_tx: spend_tx.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: now_sec() + 60, - check_every: 1, +fn refund_nft_maker_payment( + setup: &NftTestSetup, + maker_payment: &SignedEthTx, + contract_type: &ContractType, + refund_type: RefundType, +) -> SignedEthTx { + let refund_args = RefundNftMakerPaymentArgs { + maker_payment_tx: maker_payment, + taker_secret_hash: &setup.taker_secret_hash, + maker_secret_hash: &setup.maker_secret_hash, + taker_secret: &setup.taker_secret, + swap_unique_data: &[], + contract_type, }; - taker_global_nft.wait_for_confirmations(confirm_input).wait().unwrap(); + match refund_type { + RefundType::Timelock => { + block_on(setup.maker_global_nft.refund_nft_maker_payment_v2_timelock(refund_args)).unwrap() + }, + RefundType::Secret => block_on(setup.maker_global_nft.refund_nft_maker_payment_v2_secret(refund_args)).unwrap(), + } +} - let my_address = block_on(taker_global_nft.my_addr()); - let balance = erc1155_balance(my_address, U256::from(4)); - assert_eq!(balance, U256::from(3)); +enum RefundType { + Timelock, + Secret, } -#[test] -fn test_nonce_several_urls() { - // Use one working and one failing URL. - let coin = eth_coin_with_random_privkey_using_urls(swap_contract(), &[GETH_RPC_URL, "http://127.0.0.1:0"]); - let my_address = block_on(coin.derivation_method().single_addr_or_err()).unwrap(); - let (old_nonce, _) = coin.clone().get_addr_nonce(my_address).wait().unwrap(); +#[derive(Copy, Clone)] +struct SwapAddresses { + swap_v2_contracts: SwapV2Contracts, + swap_contract_address: Address, + fallback_swap_contract_address: Address, +} - // Send a payment to increase the nonce. - coin.send_to_address(my_address, 200000000.into()).wait().unwrap(); +#[allow(dead_code)] +/// Needed for Geth taker or maker swap v2 tests +impl SwapAddresses { + fn init() -> Self { + Self { + swap_contract_address: swap_contract(), + fallback_swap_contract_address: swap_contract(), + swap_v2_contracts: SwapV2Contracts { + maker_swap_v2_contract: maker_swap_v2(), + taker_swap_v2_contract: taker_swap_v2(), + nft_maker_swap_v2_contract: geth_nft_maker_swap_v2(), + }, + } + } +} - let (new_nonce, _) = coin.get_addr_nonce(my_address).wait().unwrap(); - assert_eq!(old_nonce + 1, new_nonce); +#[allow(dead_code)] +/// Needed for eth or erc20 v2 activation in Geth tests +fn eth_coin_v2_activation_with_random_privkey( + ticker: &str, + conf: &Json, + swap_addr: SwapAddresses, + erc20: bool, +) -> EthCoin { + let build_policy = EthPrivKeyBuildPolicy::IguanaPrivKey(random_secp256k1_secret()); + let node = EthNode { + url: GETH_RPC_URL.to_string(), + komodo_proxy: false, + }; + let platform_request = EthActivationV2Request { + nodes: vec![node], + rpc_mode: Default::default(), + swap_contract_address: swap_addr.swap_contract_address, + swap_v2_contracts: Some(swap_addr.swap_v2_contracts), + fallback_swap_contract: Some(swap_addr.fallback_swap_contract_address), + contract_supports_watchers: false, + mm2: None, + required_confirmations: None, + priv_key_policy: Default::default(), + enable_params: Default::default(), + path_to_address: Default::default(), + gap_limit: None, + }; + let coin = block_on(eth_coin_from_conf_and_request_v2( + &MM_CTX1, + ticker, + conf, + platform_request, + build_policy, + )) + .unwrap(); + let my_address = block_on(coin.my_addr()); + fill_eth(my_address, U256::from(10).pow(U256::from(20))); + fill_erc20(my_address, U256::from(10000000000u64)); + if erc20 { + let coin_type = EthCoinType::Erc20 { + platform: ETH.to_string(), + token_addr: erc20_contract(), + }; + let coin = block_on(coin.set_coin_type(coin_type)); + return coin; + } + coin } +#[cfg(feature = "sepolia-taker-swap-v2-tests")] #[test] -fn test_nonce_lock() { - use crate::common::Future01CompatExt; - use futures::future::join_all; +fn send_and_refund_taker_funding_by_secret_eth() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); - let coin = eth_coin_with_random_privkey(swap_contract()); - let my_address = block_on(coin.derivation_method().single_addr_or_err()).unwrap(); - let futures = (0..5).map(|_| coin.send_to_address(my_address, 200000000.into()).compat()); - let results = block_on(join_all(futures)); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); - // make sure all transactions are successful - for result in results { - result.unwrap(); - } + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let funding_time_lock = now_sec() + 3000; + let payment_time_lock = now_sec() + 1000; + + let taker_address = block_on(taker_coin.my_addr()); + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + thread::sleep(Duration::from_secs(2)); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ETH funding, tx hash: {:02x}", funding_tx.tx_hash()); + + let refund_args = RefundFundingSecretArgs { + funding_tx: &funding_tx, + funding_time_lock, + payment_time_lock, + maker_pubkey: maker_pub, + taker_secret: &taker_secret, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + dex_fee, + premium_amount: Default::default(), + trading_amount, + swap_unique_data: &[], + watcher_reward: false, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx_refund = block_on(taker_coin.refund_taker_funding_secret(refund_args)).unwrap(); + log!( + "Taker refunded ETH funding by secret, tx hash: {:02x}", + funding_tx_refund.tx_hash() + ); + + wait_for_confirmations(&taker_coin, &funding_tx_refund, 100); +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn send_and_refund_taker_funding_by_secret_erc20() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let erc20_conf = &sepolia_erc20_dev_conf(&sepolia_erc20_contract_checksum()); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + + let taker_address = block_on(taker_coin.my_addr()); + + let funding_time_lock = now_sec() + 3000; + let payment_time_lock = now_sec() + 1000; + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ERC20 funding, tx hash: {:02x}", funding_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &funding_tx, 200); + + let refund_args = RefundFundingSecretArgs { + funding_tx: &funding_tx, + funding_time_lock, + payment_time_lock, + maker_pubkey: &taker_coin.derive_htlc_pubkey_v2(&[]), + taker_secret: &taker_secret, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + dex_fee, + premium_amount: Default::default(), + trading_amount, + swap_unique_data: &[], + watcher_reward: false, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx_refund = block_on(taker_coin.refund_taker_funding_secret(refund_args)).unwrap(); + log!( + "Taker refunded ERC20 funding by secret, tx hash: {:02x}", + funding_tx_refund.tx_hash() + ); + wait_for_confirmations(&taker_coin, &funding_tx_refund, 200); +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn send_and_refund_taker_funding_exceed_pre_approve_timelock_eth() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + + let taker_address = block_on(taker_coin.my_addr()); + + // if TakerPaymentState is `PaymentSent` then timestamp should exceed payment pre-approve lock time (funding_time_lock) + let funding_time_lock = now_sec() - 3000; + let payment_time_lock = now_sec() + 1000; + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ETH funding, tx hash: {:02x}", funding_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &funding_tx, 100); + + let tx_type_with_secret_hash = SwapTxTypeWithSecretHash::TakerPaymentV2 { + maker_secret_hash: &maker_secret_hash, + taker_secret_hash: &taker_secret_hash, + }; + + let refund_args = RefundTakerPaymentArgs { + payment_tx: &funding_tx.to_bytes(), + time_lock: payment_time_lock, + maker_pub: maker_pub.as_bytes(), + tx_type_with_secret_hash, + swap_unique_data: &[], + watcher_reward: false, + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx_refund = block_on(taker_coin.refund_taker_funding_timelock(refund_args)).unwrap(); + log!( + "Taker refunded ETH funding after pre-approval lock time was exceeded, tx hash: {:02x}", + funding_tx_refund.tx_hash() + ); + + wait_for_confirmations(&taker_coin, &funding_tx_refund, 100); +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn taker_send_approve_and_spend_eth() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let funding_time_lock = now_sec() + 3000; + let payment_time_lock = now_sec() + 600; + + let taker_address = block_on(taker_coin.my_addr()); + let maker_address = block_on(maker_coin.my_addr()); + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ETH funding, tx hash: {:02x}", funding_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &funding_tx, 100); + + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + let validate = ValidateTakerFundingArgs { + funding_tx: &funding_tx, + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + taker_pub, + dex_fee, + premium_amount: Default::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + block_on(maker_coin.validate_taker_funding(validate)).unwrap(); + + let approve_args = GenTakerFundingSpendArgs { + funding_tx: &funding_tx, + maker_pub, + taker_pub, + funding_time_lock, + taker_secret_hash: &taker_secret_hash, + taker_payment_time_lock: funding_time_lock, + maker_secret_hash: &maker_secret_hash, + }; + let preimage = TxPreimageWithSig { + preimage: funding_tx.clone(), + signature: taker_coin.parse_signature(&[0u8; 65]).unwrap(), + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let taker_approve_tx = + block_on(taker_coin.sign_and_send_taker_funding_spend(&preimage, &approve_args, &[])).unwrap(); + log!( + "Taker approved ETH payment, tx hash: {:02x}", + taker_approve_tx.tx_hash() + ); + wait_for_confirmations(&taker_coin, &taker_approve_tx, 100); + + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let check_taker_approved_tx = block_on(maker_coin.search_for_taker_funding_spend(&taker_approve_tx, 0u64, &[])) + .unwrap() + .unwrap(); + match check_taker_approved_tx { + FundingTxSpend::TransferredToTakerPayment(tx) => { + assert_eq!(tx, taker_approve_tx); + }, + FundingTxSpend::RefundedTimelock(_) | FundingTxSpend::RefundedSecret { .. } => { + panic!("Wrong FundingTxSpend variant, expected TransferredToTakerPayment") + }, + }; + + let dex_fee_pub = sepolia_taker_swap_v2(); + let spend_args = GenTakerPaymentSpendArgs { + taker_tx: &taker_approve_tx, + time_lock: payment_time_lock, + maker_secret_hash: &maker_secret_hash, + maker_pub, + maker_address: &maker_address, + taker_pub, + dex_fee_pub: dex_fee_pub.as_bytes(), + dex_fee, + premium_amount: Default::default(), + trading_amount, + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let spend_tx = + block_on(maker_coin.sign_and_broadcast_taker_payment_spend(&preimage, &spend_args, &maker_secret, &[])) + .unwrap(); + log!("Maker spent ETH payment, tx hash: {:02x}", spend_tx.tx_hash()); + wait_for_confirmations(&maker_coin, &spend_tx, 100); + block_on(taker_coin.wait_for_taker_payment_spend(&spend_tx, 0u64, payment_time_lock)).unwrap(); +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn taker_send_approve_and_spend_erc20() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let erc20_conf = &sepolia_erc20_dev_conf(&sepolia_erc20_contract_checksum()); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let funding_time_lock = now_sec() + 3000; + let payment_time_lock = now_sec() + 600; + + let taker_address = block_on(taker_coin.my_addr()); + let maker_address = block_on(maker_coin.my_addr()); + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ERC20 funding, tx hash: {:02x}", funding_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &funding_tx, 100); + + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + let validate = ValidateTakerFundingArgs { + funding_tx: &funding_tx, + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + taker_pub, + dex_fee, + premium_amount: Default::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + block_on(maker_coin.validate_taker_funding(validate)).unwrap(); + + let approve_args = GenTakerFundingSpendArgs { + funding_tx: &funding_tx, + maker_pub, + taker_pub, + funding_time_lock, + taker_secret_hash: &taker_secret_hash, + taker_payment_time_lock: funding_time_lock, + maker_secret_hash: &maker_secret_hash, + }; + let preimage = TxPreimageWithSig { + preimage: funding_tx.clone(), + signature: taker_coin.parse_signature(&[0u8; 65]).unwrap(), + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let taker_approve_tx = + block_on(taker_coin.sign_and_send_taker_funding_spend(&preimage, &approve_args, &[])).unwrap(); + log!( + "Taker approved ERC20 payment, tx hash: {:02x}", + taker_approve_tx.tx_hash() + ); + wait_for_confirmations(&taker_coin, &taker_approve_tx, 100); + + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let check_taker_approved_tx = block_on(maker_coin.search_for_taker_funding_spend(&taker_approve_tx, 0u64, &[])) + .unwrap() + .unwrap(); + match check_taker_approved_tx { + FundingTxSpend::TransferredToTakerPayment(tx) => { + assert_eq!(tx, taker_approve_tx); + }, + FundingTxSpend::RefundedTimelock(_) | FundingTxSpend::RefundedSecret { .. } => { + panic!("Wrong FundingTxSpend variant, expected TransferredToTakerPayment") + }, + }; + + let dex_fee_pub = sepolia_taker_swap_v2(); + let spend_args = GenTakerPaymentSpendArgs { + taker_tx: &taker_approve_tx, + time_lock: payment_time_lock, + maker_secret_hash: &maker_secret_hash, + maker_pub, + maker_address: &maker_address, + taker_pub, + dex_fee_pub: dex_fee_pub.as_bytes(), + dex_fee, + premium_amount: Default::default(), + trading_amount, + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let spend_tx = + block_on(maker_coin.sign_and_broadcast_taker_payment_spend(&preimage, &spend_args, &maker_secret, &[])) + .unwrap(); + log!("Maker spent ERC20 payment, tx hash: {:02x}", spend_tx.tx_hash()); + block_on(taker_coin.wait_for_taker_payment_spend(&spend_tx, 0u64, payment_time_lock)).unwrap(); +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn send_and_refund_taker_funding_exceed_payment_timelock_eth() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let funding_time_lock = now_sec() + 3000; + let payment_time_lock = now_sec() - 1000; + + let taker_address = block_on(taker_coin.my_addr()); + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ETH funding, tx hash: {:02x}", funding_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &funding_tx, 100); + + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + let approve_args = GenTakerFundingSpendArgs { + funding_tx: &funding_tx, + maker_pub, + taker_pub, + funding_time_lock, + taker_secret_hash: &taker_secret_hash, + taker_payment_time_lock: funding_time_lock, + maker_secret_hash: &maker_secret_hash, + }; + let preimage = TxPreimageWithSig { + preimage: funding_tx.clone(), + signature: taker_coin.parse_signature(&[0u8; 65]).unwrap(), + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let taker_approve_tx = + block_on(taker_coin.sign_and_send_taker_funding_spend(&preimage, &approve_args, &[])).unwrap(); + log!( + "Taker approved ETH payment, tx hash: {:02x}", + taker_approve_tx.tx_hash() + ); + wait_for_confirmations(&taker_coin, &taker_approve_tx, 100); + + let tx_type_with_secret_hash = SwapTxTypeWithSecretHash::TakerPaymentV2 { + maker_secret_hash: &maker_secret_hash, + taker_secret_hash: &taker_secret_hash, + }; + let refund_args = RefundTakerPaymentArgs { + payment_tx: &funding_tx.to_bytes(), + time_lock: payment_time_lock, + maker_pub: maker_pub.as_bytes(), + tx_type_with_secret_hash, + swap_unique_data: &[], + watcher_reward: false, + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx_refund = block_on(taker_coin.refund_taker_funding_timelock(refund_args)).unwrap(); + log!( + "Taker refunded ETH funding after payment lock time was exceeded, tx hash: {:02x}", + funding_tx_refund.tx_hash() + ); + wait_for_confirmations(&taker_coin, &funding_tx_refund, 100); +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn send_and_refund_taker_funding_exceed_payment_timelock_erc20() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let erc20_conf = &sepolia_erc20_dev_conf(&sepolia_erc20_contract_checksum()); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let funding_time_lock = now_sec() + 29; + let payment_time_lock = now_sec() + 15; + + let taker_address = block_on(taker_coin.my_addr()); + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ERC20 funding, tx hash: {:02x}", funding_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &funding_tx, 100); + thread::sleep(Duration::from_secs(16)); + + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + let approve_args = GenTakerFundingSpendArgs { + funding_tx: &funding_tx, + maker_pub, + taker_pub, + funding_time_lock, + taker_secret_hash: &taker_secret_hash, + taker_payment_time_lock: funding_time_lock, + maker_secret_hash: &maker_secret_hash, + }; + let preimage = TxPreimageWithSig { + preimage: funding_tx.clone(), + signature: taker_coin.parse_signature(&[0u8; 65]).unwrap(), + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let taker_approve_tx = + block_on(taker_coin.sign_and_send_taker_funding_spend(&preimage, &approve_args, &[])).unwrap(); + log!( + "Taker approved ERC20 payment, tx hash: {:02x}", + taker_approve_tx.tx_hash() + ); + wait_for_confirmations(&taker_coin, &taker_approve_tx, 100); + + let tx_type_with_secret_hash = SwapTxTypeWithSecretHash::TakerPaymentV2 { + maker_secret_hash: &maker_secret_hash, + taker_secret_hash: &taker_secret_hash, + }; + let refund_args = RefundTakerPaymentArgs { + payment_tx: &funding_tx.to_bytes(), + time_lock: payment_time_lock, + maker_pub: maker_pub.as_bytes(), + tx_type_with_secret_hash, + swap_unique_data: &[], + watcher_reward: false, + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx_refund = block_on(taker_coin.refund_taker_funding_timelock(refund_args)).unwrap(); + log!( + "Taker refunded ERC20 funding after payment lock time was exceeded, tx hash: {:02x}", + funding_tx_refund.tx_hash() + ); + wait_for_confirmations(&taker_coin, &funding_tx_refund, 100); +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn send_and_refund_taker_funding_exceed_pre_approve_timelock_erc20() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let erc20_conf = &sepolia_erc20_dev_conf(&sepolia_erc20_contract_checksum()); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + + let taker_address = block_on(taker_coin.my_addr()); + + // if TakerPaymentState is `PaymentSent` then timestamp should exceed payment pre-approve lock time (funding_time_lock) + let funding_time_lock = now_sec() + 29; + let payment_time_lock = now_sec() + 1000; + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ERC20 funding, tx hash: {:02x}", funding_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &funding_tx, 150); + thread::sleep(Duration::from_secs(29)); + + let tx_type_with_secret_hash = SwapTxTypeWithSecretHash::TakerPaymentV2 { + maker_secret_hash: &maker_secret_hash, + taker_secret_hash: &taker_secret_hash, + }; + + let refund_args = RefundTakerPaymentArgs { + payment_tx: &funding_tx.to_bytes(), + time_lock: payment_time_lock, + maker_pub: maker_pub.as_bytes(), + tx_type_with_secret_hash, + swap_unique_data: &[], + watcher_reward: false, + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx_refund = block_on(taker_coin.refund_taker_funding_timelock(refund_args)).unwrap(); + log!( + "Taker refunded ERC20 funding after pre-approval lock time was exceeded, tx hash: {:02x}", + funding_tx_refund.tx_hash() + ); + wait_for_confirmations(&taker_coin, &funding_tx_refund, 150); +} + +#[cfg(feature = "sepolia-maker-swap-v2-tests")] +#[test] +fn send_maker_payment_and_refund_timelock_eth() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let payment_time_lock = now_sec() - 1000; + + let maker_address = block_on(maker_coin.my_addr()); + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let payment_args = SendMakerPaymentArgs { + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + taker_pub, + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx = block_on(maker_coin.send_maker_payment_v2(payment_args)).unwrap(); + log!("Maker sent ETH payment, tx hash: {:02x}", payment_tx.tx_hash()); + wait_for_confirmations(&maker_coin, &payment_tx, 100); + + let tx_type_with_secret_hash = SwapTxTypeWithSecretHash::MakerPaymentV2 { + maker_secret_hash: &maker_secret_hash, + taker_secret_hash: &taker_secret_hash, + }; + let refund_args = RefundMakerPaymentTimelockArgs { + payment_tx: &payment_tx.to_bytes(), + time_lock: payment_time_lock, + taker_pub: &taker_pub.to_bytes(), + tx_type_with_secret_hash, + swap_unique_data: &[], + watcher_reward: false, + amount: trading_amount, + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx_refund = block_on(maker_coin.refund_maker_payment_v2_timelock(refund_args)).unwrap(); + log!( + "Maker refunded ETH payment after timelock, tx hash: {:02x}", + payment_tx_refund.tx_hash() + ); + wait_for_confirmations(&maker_coin, &payment_tx_refund, 100); +} + +#[cfg(feature = "sepolia-maker-swap-v2-tests")] +#[test] +fn send_maker_payment_and_refund_timelock_erc20() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let erc20_conf = &sepolia_erc20_dev_conf(&sepolia_erc20_contract_checksum()); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let payment_time_lock = now_sec() - 1000; + + let maker_address = block_on(maker_coin.my_addr()); + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let payment_args = SendMakerPaymentArgs { + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + taker_pub, + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx = block_on(maker_coin.send_maker_payment_v2(payment_args)).unwrap(); + log!("Maker sent ERC20 payment, tx hash: {:02x}", payment_tx.tx_hash()); + wait_for_confirmations(&maker_coin, &payment_tx, 100); + + let tx_type_with_secret_hash = SwapTxTypeWithSecretHash::MakerPaymentV2 { + maker_secret_hash: &maker_secret_hash, + taker_secret_hash: &taker_secret_hash, + }; + let refund_args = RefundMakerPaymentTimelockArgs { + payment_tx: &payment_tx.to_bytes(), + time_lock: payment_time_lock, + taker_pub: &taker_pub.to_bytes(), + tx_type_with_secret_hash, + swap_unique_data: &[], + watcher_reward: false, + amount: trading_amount, + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx_refund = block_on(maker_coin.refund_maker_payment_v2_timelock(refund_args)).unwrap(); + log!( + "Maker refunded ERC20 payment after timelock, tx hash: {:02x}", + payment_tx_refund.tx_hash() + ); + wait_for_confirmations(&maker_coin, &payment_tx_refund, 100); +} + +#[cfg(feature = "sepolia-maker-swap-v2-tests")] +#[test] +fn send_maker_payment_and_refund_secret_eth() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let payment_time_lock = now_sec() + 1000; + + let maker_address = block_on(maker_coin.my_addr()); + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let payment_args = SendMakerPaymentArgs { + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + taker_pub, + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx = block_on(maker_coin.send_maker_payment_v2(payment_args)).unwrap(); + log!("Maker sent ETH payment, tx hash: {:02x}", payment_tx.tx_hash()); + wait_for_confirmations(&maker_coin, &payment_tx, 100); + + let refund_args = RefundMakerPaymentSecretArgs { + maker_payment_tx: &payment_tx, + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + taker_secret: &taker_secret, + taker_pub, + swap_unique_data: &[], + amount: trading_amount, + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx_refund = block_on(maker_coin.refund_maker_payment_v2_secret(refund_args)).unwrap(); + log!( + "Maker refunded ETH payment using taker secret, tx hash: {:02x}", + payment_tx_refund.tx_hash() + ); + wait_for_confirmations(&maker_coin, &payment_tx_refund, 100); +} + +#[cfg(feature = "sepolia-maker-swap-v2-tests")] +#[test] +fn send_maker_payment_and_refund_secret_erc20() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let erc20_conf = &sepolia_erc20_dev_conf(&sepolia_erc20_contract_checksum()); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let payment_time_lock = now_sec() + 1000; + + let maker_address = block_on(maker_coin.my_addr()); + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let payment_args = SendMakerPaymentArgs { + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + taker_pub, + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx = block_on(maker_coin.send_maker_payment_v2(payment_args)).unwrap(); + log!("Maker sent ERC20 payment, tx hash: {:02x}", payment_tx.tx_hash()); + wait_for_confirmations(&maker_coin, &payment_tx, 100); + + let refund_args = RefundMakerPaymentSecretArgs { + maker_payment_tx: &payment_tx, + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + taker_secret: &taker_secret, + taker_pub, + swap_unique_data: &[], + amount: trading_amount, + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx_refund = block_on(maker_coin.refund_maker_payment_v2_secret(refund_args)).unwrap(); + log!( + "Maker refunded ERC20 payment using taker secret, tx hash: {:02x}", + payment_tx_refund.tx_hash() + ); + wait_for_confirmations(&maker_coin, &payment_tx_refund, 100); +} + +#[cfg(feature = "sepolia-maker-swap-v2-tests")] +#[test] +fn send_and_spend_maker_payment_eth() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let payment_time_lock = now_sec() + 1000; + + let maker_address = block_on(maker_coin.my_addr()); + let taker_address = block_on(taker_coin.my_addr()); + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let payment_args = SendMakerPaymentArgs { + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + taker_pub, + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx = block_on(maker_coin.send_maker_payment_v2(payment_args)).unwrap(); + log!("Maker sent ETH payment, tx hash: {:02x}", payment_tx.tx_hash()); + wait_for_confirmations(&maker_coin, &payment_tx, 100); + + let validation_args = ValidateMakerPaymentArgs { + maker_payment_tx: &payment_tx, + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + maker_pub, + swap_unique_data: &[], + }; + block_on(taker_coin.validate_maker_payment_v2(validation_args)).unwrap(); + log!("Taker validated maker ETH payment"); + + let spend_args = SpendMakerPaymentArgs { + maker_payment_tx: &payment_tx, + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_secret: &maker_secret, + maker_pub, + swap_unique_data: &[], + amount: trading_amount, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let spend_tx = block_on(taker_coin.spend_maker_payment_v2(spend_args)).unwrap(); + log!("Taker spent maker ETH payment, tx hash: {:02x}", spend_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &spend_tx, 100); +} + +#[cfg(feature = "sepolia-maker-swap-v2-tests")] +#[test] +fn send_and_spend_maker_payment_erc20() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let erc20_conf = &sepolia_erc20_dev_conf(&sepolia_erc20_contract_checksum()); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let payment_time_lock = now_sec() + 1000; + + let maker_address = block_on(maker_coin.my_addr()); + let taker_address = block_on(taker_coin.my_addr()); + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let payment_args = SendMakerPaymentArgs { + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + taker_pub, + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx = block_on(maker_coin.send_maker_payment_v2(payment_args)).unwrap(); + log!("Maker sent ERC20 payment, tx hash: {:02x}", payment_tx.tx_hash()); + wait_for_confirmations(&maker_coin, &payment_tx, 100); + + let validation_args = ValidateMakerPaymentArgs { + maker_payment_tx: &payment_tx, + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + maker_pub, + swap_unique_data: &[], + }; + block_on(taker_coin.validate_maker_payment_v2(validation_args)).unwrap(); + log!("Taker validated maker ERC20 payment"); + + let spend_args = SpendMakerPaymentArgs { + maker_payment_tx: &payment_tx, + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_secret: &maker_secret, + maker_pub, + swap_unique_data: &[], + amount: trading_amount, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let spend_tx = block_on(taker_coin.spend_maker_payment_v2(spend_args)).unwrap(); + log!("Taker spent maker ERC20 payment, tx hash: {:02x}", spend_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &spend_tx, 100); +} + +#[test] +fn test_eth_erc20_hd() { + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + // Withdraw from HD account 0, change address 0, index 0 + let path_to_address = HDAccountAddressId::default(); + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + let eth_enable = block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &["ERC20DEV"], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address), + )); + let activation_result = match eth_enable { + EthWithTokensActivationResult::HD(hd) => hd, + _ => panic!("Expected EthWithTokensActivationResult::HD"), + }; + let balance = match activation_result.wallet_balance { + EnableCoinBalanceMap::HD(hd) => hd, + _ => panic!("Expected EnableCoinBalance::HD"), + }; + let account = balance.accounts.get(0).expect("Expected account at index 0"); + assert_eq!( + account.addresses[0].address, + "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93" + ); + assert_eq!(account.addresses[0].balance.len(), 2); + assert!(account.addresses[0].balance.contains_key("ETH")); + assert!(account.addresses[0].balance.contains_key("ERC20DEV")); + + block_on(mm_hd.stop()).unwrap(); + + // Enable HD account 0, change address 0, index 1 + let path_to_address = HDAccountAddressId { + account_id: 0, + chain: Bip44Chain::External, + address_id: 1, + }; + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + let eth_enable = block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &["ERC20DEV"], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address), + )); + let activation_result = match eth_enable { + EthWithTokensActivationResult::HD(hd) => hd, + _ => panic!("Expected EthWithTokensActivationResult::HD"), + }; + let balance = match activation_result.wallet_balance { + EnableCoinBalanceMap::HD(hd) => hd, + _ => panic!("Expected EnableCoinBalance::HD"), + }; + let account = balance.accounts.get(0).expect("Expected account at index 0"); + assert_eq!( + account.addresses[1].address, + "0xDe841899aB4A22E23dB21634e54920aDec402397" + ); + assert_eq!(account.addresses[0].balance.len(), 2); + assert!(account.addresses[0].balance.contains_key("ETH")); + assert!(account.addresses[0].balance.contains_key("ERC20DEV")); + + let get_new_address = block_on(get_new_address(&mm_hd, "ETH", 0, Some(Bip44Chain::External))); + assert!(get_new_address.new_address.balance.contains_key("ETH")); + // Make sure balance is returned for any token enabled with ETH as platform coin + assert!(get_new_address.new_address.balance.contains_key("ERC20DEV")); + assert_eq!( + get_new_address.new_address.address, + "0x4249E165a68E4FF9C41B1C3C3b4245c30ecB43CC" + ); + // Make sure that the address is also added to tokens + let account_balance = block_on(account_balance(&mm_hd, "ERC20DEV", 0, Bip44Chain::External)); + assert_eq!( + account_balance.addresses[2].address, + "0x4249E165a68E4FF9C41B1C3C3b4245c30ecB43CC" + ); + + block_on(mm_hd.stop()).unwrap(); + + // Enable HD account 77, change address 0, index 7 + let path_to_address = HDAccountAddressId { + account_id: 77, + chain: Bip44Chain::External, + address_id: 7, + }; + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + let eth_enable = block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &["ERC20DEV"], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address), + )); + let activation_result = match eth_enable { + EthWithTokensActivationResult::HD(hd) => hd, + _ => panic!("Expected EthWithTokensActivationResult::HD"), + }; + let balance = match activation_result.wallet_balance { + EnableCoinBalanceMap::HD(hd) => hd, + _ => panic!("Expected EnableCoinBalance::HD"), + }; + let account = balance.accounts.get(0).expect("Expected account at index 0"); + assert_eq!( + account.addresses[7].address, + "0xa420a4DBd8C50e6240014Db4587d2ec8D0cE0e6B" + ); + assert_eq!(account.addresses[0].balance.len(), 2); + assert!(account.addresses[0].balance.contains_key("ETH")); + assert!(account.addresses[0].balance.contains_key("ERC20DEV")); + + block_on(mm_hd.stop()).unwrap(); +} + +#[test] +fn test_enable_custom_erc20() { + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let coins = json!([eth_dev_conf()]); + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + let path_to_address = HDAccountAddressId::default(); + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + // Enable platform coin in HD mode + block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &[], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address.clone()), + )); + + // Test `get_token_info` rpc, we also use it to get the token symbol to use it as the ticker + let protocol = erc20_dev_conf(&erc20_contract_checksum())["protocol"].clone(); + let TokenInfo::ERC20(custom_token_info) = block_on(get_token_info(&mm_hd, protocol.clone())).info; + let ticker = custom_token_info.symbol; + assert_eq!(ticker, "QTC"); + assert_eq!(custom_token_info.decimals, 8); + + // Enable the custom token in HD mode + block_on(enable_erc20_token_v2( + &mm_hd, + &ticker, + Some(protocol.clone()), + 60, + Some(path_to_address.clone()), + )) + .unwrap(); + + // Test that the custom token is wallet only by using it in a swap + let buy = block_on(mm_hd.rpc(&json!({ + "userpass": mm_hd.userpass, + "method": "buy", + "base": "ETH", + "rel": ticker, + "price": "1", + "volume": "1", + }))) + .unwrap(); + assert!(!buy.0.is_success(), "buy success, but should fail: {}", buy.1); + assert!( + buy.1.contains(&format!("Rel coin {} is wallet only", ticker)), + "Expected error message indicating that the token is wallet only, but got: {}", + buy.1 + ); + + // Enabling the same custom token using a different ticker should fail + let err = block_on(enable_erc20_token_v2( + &mm_hd, + "ERC20DEV", + Some(protocol.clone()), + 60, + Some(path_to_address), + )) + .unwrap_err(); + let expected_error_type = "CustomTokenError"; + assert_eq!(err["error_type"], expected_error_type); + let expected_error_data = json!({ + "TokenWithSameContractAlreadyActivated": { + "ticker": ticker, + "contract_address": protocol["protocol_data"]["contract_address"] + } + }); + assert_eq!(err["error_data"], expected_error_data); + + // Disable the custom token + block_on(disable_coin(&mm_hd, &ticker, true)); +} + +#[test] +fn test_enable_custom_erc20_with_duplicate_contract_in_config() { + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let erc20_dev_conf = erc20_dev_conf(&erc20_contract_checksum()); + let coins = json!([eth_dev_conf(), erc20_dev_conf]); + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + let path_to_address = HDAccountAddressId::default(); + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + // Enable platform coin in HD mode + block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &[], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address.clone()), + )); + + let protocol = erc20_dev_conf["protocol"].clone(); + // Enable the custom token in HD mode. + // Since the contract is already in the coins config, this should fail with an error + // that specifies the ticker in config so that the user can enable the right coin. + let err = block_on(enable_erc20_token_v2( + &mm_hd, + "QTC", + Some(protocol.clone()), + 60, + Some(path_to_address.clone()), + )) + .unwrap_err(); + let expected_error_type = "CustomTokenError"; + assert_eq!(err["error_type"], expected_error_type); + let expected_error_data = json!({ + "DuplicateContractInConfig": { + "ticker_in_config": "ERC20DEV" + } + }); + assert_eq!(err["error_data"], expected_error_data); + + // Another way is to use the `get_token_info` RPC and use the config ticker to enable the token. + let custom_token_info = block_on(get_token_info(&mm_hd, protocol)); + assert!(custom_token_info.config_ticker.is_some()); + let config_ticker = custom_token_info.config_ticker.unwrap(); + assert_eq!(config_ticker, "ERC20DEV"); + // Parameters passed here are for normal enabling of a coin in config and not for a custom token + block_on(enable_erc20_token_v2( + &mm_hd, + &config_ticker, + None, + 60, + Some(path_to_address), + )) + .unwrap(); + + // Disable the custom token, this to check that it was enabled correctly + block_on(disable_coin(&mm_hd, &config_ticker, true)); } diff --git a/mm2src/mm2_main/tests/docker_tests/mod.rs b/mm2src/mm2_main/tests/docker_tests/mod.rs index 848e43c1eb..4b969b9fec 100644 --- a/mm2src/mm2_main/tests/docker_tests/mod.rs +++ b/mm2src/mm2_main/tests/docker_tests/mod.rs @@ -4,12 +4,13 @@ mod docker_ordermatch_tests; mod docker_tests_inner; mod eth_docker_tests; pub mod qrc20_tests; +#[cfg(feature = "enable-sia")] mod sia_docker_tests; mod slp_tests; -#[cfg(feature = "enable-solana")] mod solana_tests; mod swap_proto_v2_tests; mod swap_watcher_tests; mod swaps_confs_settings_sync_tests; mod swaps_file_lock_tests; +mod tendermint_tests; // dummy test helping IDE to recognize this as test module #[test] diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index 72deda0e4d..cfbd2df664 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -11,13 +11,12 @@ use coins::{CheckIfMyPaymentSentArgs, ConfirmPaymentInput, DexFee, FeeApproxStag SwapTxTypeWithSecretHash, TradePreimageValue, TransactionEnum, ValidateFeeArgs, ValidatePaymentInput, WaitForHTLCTxSpendArgs}; use common::log::debug; -use common::{temp_dir, DEX_FEE_ADDR_RAW_PUBKEY}; +use common::{block_on_f01, temp_dir, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::Secp256k1Secret; use ethereum_types::H160; -use futures01::Future; use http::StatusCode; use mm2_core::mm_ctx::{MmArc, MmCtxBuilder}; -use mm2_main::mm2::lp_swap::{dex_fee_amount, max_taker_vol_from_available}; +use mm2_main::lp_swap::{dex_fee_amount, max_taker_vol_from_available}; use mm2_number::BigDecimal; use mm2_rpc::data::legacy::{CoinInitResponse, OrderbookResponse}; use mm2_test_helpers::structs::{trade_preimage_error, RpcErrorResponse, RpcSuccessResponse, TransactionDetails}; @@ -78,9 +77,7 @@ impl QtumDockerOps { match self.coin.as_ref().rpc_client { UtxoRpcClientEnum::Native(ref native) => { - let result = native - .create_contract(&bytecode.into(), gas_limit, gas_price, sender) - .wait() + let result = block_on_f01(native.create_contract(&bytecode.into(), gas_limit, gas_price, sender)) .expect("!createcontract"); result.address.0.into() }, @@ -165,14 +162,8 @@ fn withdraw_and_send(mm: &MarketMakerIt, coin: &str, to: &str, amount: f64) { fn test_taker_spends_maker_payment() { let (_ctx, maker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); let (_ctx, taker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 1.into()); - let maker_old_balance = maker_coin - .my_spendable_balance() - .wait() - .expect("Error on get maker balance"); - let taker_old_balance = taker_coin - .my_spendable_balance() - .wait() - .expect("Error on get taker balance"); + let maker_old_balance = block_on_f01(maker_coin.my_spendable_balance()).expect("Error on get maker balance"); + let taker_old_balance = block_on_f01(taker_coin.my_spendable_balance()).expect("Error on get taker balance"); assert_eq!(maker_old_balance, BigDecimal::from(10)); assert_eq!(taker_old_balance, BigDecimal::from(1)); @@ -194,7 +185,7 @@ fn test_taker_spends_maker_payment() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let payment = block_on(maker_coin.send_maker_payment(maker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Maker payment: {:?}", payment_tx_hash); @@ -210,7 +201,7 @@ fn test_taker_spends_maker_payment() { wait_until, check_every, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let input = ValidatePaymentInput { payment_tx: payment_tx_hex.clone(), @@ -249,16 +240,10 @@ fn test_taker_spends_maker_payment() { wait_until, check_every, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); - - let maker_balance = maker_coin - .my_spendable_balance() - .wait() - .expect("Error on get maker balance"); - let taker_balance = taker_coin - .my_spendable_balance() - .wait() - .expect("Error on get taker balance"); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); + + let maker_balance = block_on_f01(maker_coin.my_spendable_balance()).expect("Error on get maker balance"); + let taker_balance = block_on_f01(taker_coin.my_spendable_balance()).expect("Error on get taker balance"); assert_eq!(maker_old_balance - amount.clone(), maker_balance); assert_eq!(taker_old_balance + amount, taker_balance); } @@ -267,14 +252,8 @@ fn test_taker_spends_maker_payment() { fn test_maker_spends_taker_payment() { let (_ctx, maker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); let (_ctx, taker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); - let maker_old_balance = maker_coin - .my_spendable_balance() - .wait() - .expect("Error on get maker balance"); - let taker_old_balance = taker_coin - .my_spendable_balance() - .wait() - .expect("Error on get taker balance"); + let maker_old_balance = block_on_f01(maker_coin.my_spendable_balance()).expect("Error on get maker balance"); + let taker_old_balance = block_on_f01(taker_coin.my_spendable_balance()).expect("Error on get taker balance"); assert_eq!(maker_old_balance, BigDecimal::from(10)); assert_eq!(taker_old_balance, BigDecimal::from(10)); @@ -296,7 +275,7 @@ fn test_maker_spends_taker_payment() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = taker_coin.send_taker_payment(taker_payment_args).wait().unwrap(); + let payment = block_on(taker_coin.send_taker_payment(taker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Taker payment: {:?}", payment_tx_hash); @@ -312,7 +291,7 @@ fn test_maker_spends_taker_payment() { wait_until, check_every, }; - maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let input = ValidatePaymentInput { payment_tx: payment_tx_hex.clone(), @@ -351,16 +330,10 @@ fn test_maker_spends_taker_payment() { wait_until, check_every, }; - maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); - - let maker_balance = maker_coin - .my_spendable_balance() - .wait() - .expect("Error on get maker balance"); - let taker_balance = taker_coin - .my_spendable_balance() - .wait() - .expect("Error on get taker balance"); + block_on_f01(maker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); + + let maker_balance = block_on_f01(maker_coin.my_spendable_balance()).expect("Error on get maker balance"); + let taker_balance = block_on_f01(taker_coin.my_spendable_balance()).expect("Error on get taker balance"); assert_eq!(maker_old_balance + amount.clone(), maker_balance); assert_eq!(taker_old_balance - amount, taker_balance); } @@ -368,7 +341,7 @@ fn test_maker_spends_taker_payment() { #[test] fn test_maker_refunds_payment() { let (_ctx, coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); - let expected_balance = coin.my_spendable_balance().wait().unwrap(); + let expected_balance = block_on_f01(coin.my_spendable_balance()).unwrap(); assert_eq!(expected_balance, BigDecimal::from(10)); let timelock = now_sec() - 200; @@ -387,7 +360,7 @@ fn test_maker_refunds_payment() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = coin.send_maker_payment(maker_payment).wait().unwrap(); + let payment = block_on(coin.send_maker_payment(maker_payment)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Maker payment: {:?}", payment_tx_hash); @@ -403,9 +376,9 @@ fn test_maker_refunds_payment() { wait_until, check_every, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let balance_after_payment = coin.my_spendable_balance().wait().unwrap(); + let balance_after_payment = block_on_f01(coin.my_spendable_balance()).unwrap(); assert_eq!(expected_balance.clone() - amount, balance_after_payment); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment_tx_hex, @@ -431,16 +404,16 @@ fn test_maker_refunds_payment() { wait_until, check_every, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let balance_after_refund = coin.my_spendable_balance().wait().unwrap(); + let balance_after_refund = block_on_f01(coin.my_spendable_balance()).unwrap(); assert_eq!(expected_balance, balance_after_refund); } #[test] fn test_taker_refunds_payment() { let (_ctx, coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); - let expected_balance = coin.my_spendable_balance().wait().unwrap(); + let expected_balance = block_on_f01(coin.my_spendable_balance()).unwrap(); assert_eq!(expected_balance, BigDecimal::from(10)); let timelock = now_sec() - 200; @@ -459,7 +432,7 @@ fn test_taker_refunds_payment() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = coin.send_taker_payment(taker_payment_args).wait().unwrap(); + let payment = block_on(coin.send_taker_payment(taker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Taker payment: {:?}", payment_tx_hash); @@ -475,9 +448,9 @@ fn test_taker_refunds_payment() { wait_until, check_every, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let balance_after_payment = coin.my_spendable_balance().wait().unwrap(); + let balance_after_payment = block_on_f01(coin.my_spendable_balance()).unwrap(); assert_eq!(expected_balance.clone() - amount, balance_after_payment); let taker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment_tx_hex, @@ -503,9 +476,9 @@ fn test_taker_refunds_payment() { wait_until, check_every, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let balance_after_refund = coin.my_spendable_balance().wait().unwrap(); + let balance_after_refund = block_on_f01(coin.my_spendable_balance()).unwrap(); assert_eq!(expected_balance, balance_after_refund); } @@ -528,7 +501,7 @@ fn test_check_if_my_payment_sent() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let payment = block_on(coin.send_maker_payment(maker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Maker payment: {:?}", payment_tx_hash); @@ -544,9 +517,9 @@ fn test_check_if_my_payment_sent() { wait_until, check_every, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let search_from_block = coin.current_block().wait().expect("!current_block") - 10; + let search_from_block = block_on_f01(coin.current_block()).expect("!current_block") - 10; let if_my_payment_sent_args = CheckIfMyPaymentSentArgs { time_lock: timelock, other_pub: &taker_pub, @@ -557,7 +530,7 @@ fn test_check_if_my_payment_sent() { amount: &amount, payment_instructions: &None, }; - let found = coin.check_if_my_payment_sent(if_my_payment_sent_args).wait().unwrap(); + let found = block_on(coin.check_if_my_payment_sent(if_my_payment_sent_args)).unwrap(); assert_eq!(found, Some(payment)); } @@ -565,7 +538,7 @@ fn test_check_if_my_payment_sent() { fn test_search_for_swap_tx_spend_taker_spent() { let (_ctx, maker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); let (_ctx, taker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 1.into()); - let search_from_block = maker_coin.current_block().wait().expect("!current_block"); + let search_from_block = block_on_f01(maker_coin.current_block()).expect("!current_block"); let timelock = now_sec() - 200; let maker_pub = maker_coin.my_public_key().unwrap(); @@ -585,7 +558,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let payment = block_on(maker_coin.send_maker_payment(maker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Maker payment: {:?}", payment_tx_hash); @@ -601,7 +574,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { wait_until, check_every, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let taker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &payment_tx_hex, time_lock: timelock, @@ -625,7 +598,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { wait_until, check_every, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock: timelock, @@ -645,7 +618,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { #[test] fn test_search_for_swap_tx_spend_maker_refunded() { let (_ctx, maker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); - let search_from_block = maker_coin.current_block().wait().expect("!current_block"); + let search_from_block = block_on_f01(maker_coin.current_block()).expect("!current_block"); let timelock = now_sec() - 200; let taker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); @@ -664,7 +637,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let payment = block_on(maker_coin.send_maker_payment(maker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Maker payment: {:?}", payment_tx_hash); @@ -680,7 +653,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { wait_until, check_every, }; - maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment_tx_hex, time_lock: timelock, @@ -705,7 +678,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { wait_until, check_every, }; - maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock: timelock, @@ -725,7 +698,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { #[test] fn test_search_for_swap_tx_spend_not_spent() { let (_ctx, maker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); - let search_from_block = maker_coin.current_block().wait().expect("!current_block"); + let search_from_block = block_on_f01(maker_coin.current_block()).expect("!current_block"); let timelock = now_sec() - 200; let taker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); @@ -744,7 +717,7 @@ fn test_search_for_swap_tx_spend_not_spent() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let payment = block_on(maker_coin.send_maker_payment(maker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Maker payment: {:?}", payment_tx_hash); @@ -760,7 +733,7 @@ fn test_search_for_swap_tx_spend_not_spent() { wait_until, check_every, }; - maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock: timelock, @@ -781,7 +754,7 @@ fn test_search_for_swap_tx_spend_not_spent() { fn test_wait_for_tx_spend() { let (_ctx, maker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); let (_ctx, taker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 1.into()); - let from_block = maker_coin.current_block().wait().expect("!current_block"); + let from_block = block_on_f01(maker_coin.current_block()).expect("!current_block"); let timelock = now_sec() - 200; let maker_pub = maker_coin.my_public_key().unwrap(); @@ -801,7 +774,7 @@ fn test_wait_for_tx_spend() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let payment = block_on(maker_coin.send_maker_payment(maker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Maker payment: {:?}", payment_tx_hash); @@ -817,22 +790,20 @@ fn test_wait_for_tx_spend() { wait_until, check_every, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); // first try to check if the wait_for_htlc_tx_spend() returns an error correctly let wait_until = wait_until_sec(5); - let tx_err = maker_coin - .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &payment_tx_hex, - secret_hash: &[], - wait_until, - from_block, - swap_contract_address: &maker_coin.swap_contract_address(), - check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: false, - }) - .wait() - .expect_err("Expected 'Waited too long' error"); + let tx_err = block_on(maker_coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &payment_tx_hex, + secret_hash: &[], + wait_until, + from_block, + swap_contract_address: &maker_coin.swap_contract_address(), + check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, + watcher_reward: false, + })) + .expect_err("Expected 'Waited too long' error"); let err = tx_err.get_plain_text_format(); log!("error: {:?}", err); @@ -860,18 +831,16 @@ fn test_wait_for_tx_spend() { }); let wait_until = wait_until_sec(120); - let found = maker_coin - .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &payment_tx_hex, - secret_hash: &[], - wait_until, - from_block, - swap_contract_address: &maker_coin.swap_contract_address(), - check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: false, - }) - .wait() - .unwrap(); + let found = block_on(maker_coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &payment_tx_hex, + secret_hash: &[], + wait_until, + from_block, + swap_contract_address: &maker_coin.swap_contract_address(), + check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, + watcher_reward: false, + })) + .unwrap(); unsafe { assert_eq!(Some(found), SPEND_TX) } } @@ -1030,7 +999,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); log!("{:?}", block_on(enable_native(&mm, "QTUM", &[], None))); - let qtum_balance = coin.my_spendable_balance().wait().expect("!my_balance"); + let qtum_balance = block_on_f01(coin.my_spendable_balance()).expect("!my_balance"); let qtum_min_tx_amount = MmNumber::from("0.000728"); // - `max_possible = balance - locked_amount`, where `locked_amount = 0` @@ -1064,6 +1033,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ // where `available = balance - locked_amount - max_trade_fee - max_fee_to_send_taker_fee` let available = &qtum_balance - &max_trade_fee - &max_fee_to_send_taker_fee; debug!("total_available: {}", available); + #[allow(clippy::redundant_clone)] // This is a false-possitive bug from clippy let min_tx_amount = qtum_min_tx_amount.clone(); let expected_max_taker_vol = max_taker_vol_from_available(MmNumber::from(available), "QTUM", "MYCOIN", &min_tx_amount) @@ -1102,10 +1072,8 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ let secret_hash = &[0; 20]; let dex_fee = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_min_tx_amount); - let _taker_fee_tx = coin - .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, dex_fee, &[], timelock) - .wait() - .expect("!send_taker_fee"); + let _taker_fee_tx = + block_on(coin.send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, dex_fee, &[], timelock)).expect("!send_taker_fee"); let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, @@ -1119,12 +1087,9 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ wait_for_confirmation_until: 0, }; - let _taker_payment_tx = coin - .send_taker_payment(taker_payment_args) - .wait() - .expect("!send_taker_payment"); + let _taker_payment_tx = block_on(coin.send_taker_payment(taker_payment_args)).expect("!send_taker_payment"); - let my_balance = coin.my_spendable_balance().wait().expect("!my_balance"); + let my_balance = block_on_f01(coin.my_spendable_balance()).expect("!my_balance"); assert_eq!( my_balance, BigDecimal::from(0u32), @@ -1518,7 +1483,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_maker_payment(maker_payment).wait().unwrap(); + let tx = block_on(coin.send_maker_payment(maker_payment)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx.tx_hex(), @@ -1527,7 +1492,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, @@ -1548,7 +1513,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -1586,7 +1551,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_taker_payment(taker_payment).wait().unwrap(); + let tx = block_on(coin.send_taker_payment(taker_payment)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx.tx_hex(), @@ -1595,7 +1560,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, @@ -1616,7 +1581,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -1740,26 +1705,23 @@ fn test_send_taker_fee_qtum() { generate_segwit_qtum_coin_with_random_privkey("QTUM", BigDecimal::try_from(0.5).unwrap(), Some(0)); let amount = BigDecimal::from_str("0.01").unwrap(); - let tx = coin - .send_taker_fee( - &DEX_FEE_ADDR_RAW_PUBKEY, - DexFee::Standard(amount.clone().into()), - &[], - 0, - ) - .wait() - .expect("!send_taker_fee"); + let tx = block_on(coin.send_taker_fee( + &DEX_FEE_ADDR_RAW_PUBKEY, + DexFee::Standard(amount.clone().into()), + &[], + 0, + )) + .expect("!send_taker_fee"); assert!(matches!(tx, TransactionEnum::UtxoTx(_)), "Expected UtxoTx"); - coin.validate_fee(ValidateFeeArgs { + block_on(coin.validate_fee(ValidateFeeArgs { fee_tx: &tx, expected_sender: coin.my_public_key().unwrap(), fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], - }) - .wait() + })) .expect("!validate_fee"); } @@ -1772,25 +1734,22 @@ fn test_send_taker_fee_qrc20() { ); let amount = BigDecimal::from_str("0.01").unwrap(); - let tx = coin - .send_taker_fee( - &DEX_FEE_ADDR_RAW_PUBKEY, - DexFee::Standard(amount.clone().into()), - &[], - 0, - ) - .wait() - .expect("!send_taker_fee"); + let tx = block_on(coin.send_taker_fee( + &DEX_FEE_ADDR_RAW_PUBKEY, + DexFee::Standard(amount.clone().into()), + &[], + 0, + )) + .expect("!send_taker_fee"); assert!(matches!(tx, TransactionEnum::UtxoTx(_)), "Expected UtxoTx"); - coin.validate_fee(ValidateFeeArgs { + block_on(coin.validate_fee(ValidateFeeArgs { fee_tx: &tx, expected_sender: coin.my_public_key().unwrap(), fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], - }) - .wait() + })) .expect("!validate_fee"); } diff --git a/mm2src/mm2_main/tests/docker_tests/sia_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/sia_docker_tests.rs new file mode 100644 index 0000000000..b8546aa218 --- /dev/null +++ b/mm2src/mm2_main/tests/docker_tests/sia_docker_tests.rs @@ -0,0 +1,118 @@ +use common::block_on; +use sia_rust::http_client::{SiaApiClient, SiaApiClientError, SiaHttpConf}; +use sia_rust::http_endpoints::{AddressBalanceRequest, AddressUtxosRequest, ConsensusTipRequest, TxpoolBroadcastRequest}; +use sia_rust::spend_policy::SpendPolicy; +use sia_rust::transaction::{SiacoinOutput, V2TransactionBuilder}; +use sia_rust::types::{Address, Currency}; +use sia_rust::{Keypair, PublicKey, SecretKey}; +use std::process::Command; +use std::str::FromStr; +use url::Url; + +#[cfg(test)] +fn mine_blocks(n: u64, addr: &Address) { + Command::new("docker") + .arg("exec") + .arg("sia-docker") + .arg("walletd") + .arg("mine") + .arg(format!("-addr={}", addr)) + .arg(format!("-n={}", n)) + .status() + .expect("Failed to execute docker command"); +} + +#[test] +fn test_sia_new_client() { + let conf = SiaHttpConf { + url: Url::parse("http://localhost:9980/").unwrap(), + password: "password".to_string(), + }; + let _api_client = block_on(SiaApiClient::new(conf)).unwrap(); +} + +#[test] +fn test_sia_client_bad_auth() { + let conf = SiaHttpConf { + url: Url::parse("http://localhost:9980/").unwrap(), + password: "foo".to_string(), + }; + let result = block_on(SiaApiClient::new(conf)); + assert!(matches!(result, Err(SiaApiClientError::UnexpectedHttpStatus(401)))); +} + +#[test] +fn test_sia_client_consensus_tip() { + let conf = SiaHttpConf { + url: Url::parse("http://localhost:9980/").unwrap(), + password: "password".to_string(), + }; + let api_client = block_on(SiaApiClient::new(conf)).unwrap(); + let _response = block_on(api_client.dispatcher(ConsensusTipRequest)).unwrap(); +} + +// This test likely needs to be removed because mine_blocks has possibility of interfering with other async tests +// related to block height +#[test] +fn test_sia_client_address_balance() { + let conf = SiaHttpConf { + url: Url::parse("http://localhost:9980/").unwrap(), + password: "password".to_string(), + }; + let api_client = block_on(SiaApiClient::new(conf)).unwrap(); + + let address = + Address::from_str("addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f").unwrap(); + mine_blocks(10, &address); + + let request = AddressBalanceRequest { address }; + let response = block_on(api_client.dispatcher(request)).unwrap(); + + let expected = Currency::new(12919594847110692864, 54210108624275221); + assert_eq!(response.siacoins, expected); + assert_eq!(expected.to_u128(), 1000000000000000000000000000000000000); +} + +#[test] +fn test_sia_client_build_tx() { + let conf = SiaHttpConf { + url: Url::parse("http://localhost:9980/").unwrap(), + password: "password".to_string(), + }; + let api_client = block_on(SiaApiClient::new(conf)).unwrap(); + let sk: SecretKey = SecretKey::from_bytes( + &hex::decode("0100000000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let pk: PublicKey = (&sk).into(); + let keypair = Keypair { public: pk, secret: sk }; + let spend_policy = SpendPolicy::PublicKey(pk); + + let address = spend_policy.address(); + + mine_blocks(201, &address); + + let utxos = block_on(api_client.dispatcher(AddressUtxosRequest { + address: address.clone(), + })) + .unwrap(); + let spend_this = utxos[0].clone(); + let vin = spend_this.clone(); + println!("utxo[0]: {:?}", spend_this); + let vout = SiacoinOutput { + value: spend_this.siacoin_output.value, + address, + }; + let tx = V2TransactionBuilder::new(0u64.into()) + .add_siacoin_input(vin, spend_policy) + .add_siacoin_output(vout) + .sign_simple(vec![&keypair]) + .unwrap() + .build(); + + let req = TxpoolBroadcastRequest { + transactions: vec![], + v2transactions: vec![tx], + }; + let _response = block_on(api_client.dispatcher(req)).unwrap(); +} diff --git a/mm2src/mm2_main/tests/docker_tests/solana_tests.rs b/mm2src/mm2_main/tests/docker_tests/solana_tests.rs deleted file mode 100644 index d1faa5d2a7..0000000000 --- a/mm2src/mm2_main/tests/docker_tests/solana_tests.rs +++ /dev/null @@ -1,144 +0,0 @@ -use crate::docker_tests::docker_tests_common::*; -use mm2_number::bigdecimal::Zero; -use mm2_test_helpers::for_tests::{disable_coin, enable_solana_with_tokens, enable_spl, sign_message, verify_message}; -use mm2_test_helpers::structs::{EnableSolanaWithTokensResponse, EnableSplResponse, RpcV2Response, SignatureResponse, - VerificationResponse}; -use serde_json as json; - -#[test] -fn test_solana_and_spl_balance_enable_spl_v2() { - let mm = _solana_supplied_node(); - let tx_history = false; - let enable_solana_with_tokens = block_on(enable_solana_with_tokens( - &mm, - "SOL-DEVNET", - &["USDC-SOL-DEVNET"], - "https://api.devnet.solana.com", - tx_history, - )); - let enable_solana_with_tokens: RpcV2Response = - json::from_value(enable_solana_with_tokens).unwrap(); - - let (_, solana_balance) = enable_solana_with_tokens - .result - .solana_addresses_infos - .into_iter() - .next() - .unwrap(); - assert!(solana_balance.balances.unwrap().spendable > 0.into()); - - let spl_balances = enable_solana_with_tokens - .result - .spl_addresses_infos - .into_iter() - .next() - .unwrap() - .1 - .balances - .unwrap(); - let usdc_spl = spl_balances.get("USDC-SOL-DEVNET").unwrap(); - assert!(usdc_spl.spendable.is_zero()); - - let enable_spl = block_on(enable_spl(&mm, "ADEX-SOL-DEVNET")); - let enable_spl: RpcV2Response = json::from_value(enable_spl).unwrap(); - assert_eq!(1, enable_spl.result.balances.len()); - - let (_, balance) = enable_spl.result.balances.into_iter().next().unwrap(); - assert!(balance.spendable > 0.into()); -} - -#[test] -fn test_sign_verify_message_solana() { - let mm = _solana_supplied_node(); - let tx_history = false; - block_on(enable_solana_with_tokens( - &mm, - "SOL-DEVNET", - &["USDC-SOL-DEVNET"], - "https://api.devnet.solana.com", - tx_history, - )); - - let response = block_on(sign_message(&mm, "SOL-DEVNET")); - let response: RpcV2Response = json::from_value(response).unwrap(); - let response = response.result; - - assert_eq!( - response.signature, - "3AoWCXHq3ACYHYEHUsCzPmRNiXn5c6kodXn9KDd1tz52e1da3dZKYXD5nrJW31XLtN6zzJiwHWtDta52w7Cd7qyE" - ); - - let response = block_on(verify_message( - &mm, - "SOL-DEVNET", - "3AoWCXHq3ACYHYEHUsCzPmRNiXn5c6kodXn9KDd1tz52e1da3dZKYXD5nrJW31XLtN6zzJiwHWtDta52w7Cd7qyE", - "FJktmyjV9aBHEShT4hfnLpr9ELywdwVtEL1w1rSWgbVf", - )); - let response: RpcV2Response = json::from_value(response).unwrap(); - let response = response.result; - - assert!(response.is_valid); -} - -#[test] -fn test_sign_verify_message_spl() { - let mm = _solana_supplied_node(); - let tx_history = false; - block_on(enable_solana_with_tokens( - &mm, - "SOL-DEVNET", - &["USDC-SOL-DEVNET"], - "https://api.devnet.solana.com", - tx_history, - )); - - block_on(enable_spl(&mm, "ADEX-SOL-DEVNET")); - - let response = block_on(sign_message(&mm, "ADEX-SOL-DEVNET")); - let response: RpcV2Response = json::from_value(response).unwrap(); - let response = response.result; - - assert_eq!( - response.signature, - "3AoWCXHq3ACYHYEHUsCzPmRNiXn5c6kodXn9KDd1tz52e1da3dZKYXD5nrJW31XLtN6zzJiwHWtDta52w7Cd7qyE" - ); - - let response = block_on(verify_message( - &mm, - "ADEX-SOL-DEVNET", - "3AoWCXHq3ACYHYEHUsCzPmRNiXn5c6kodXn9KDd1tz52e1da3dZKYXD5nrJW31XLtN6zzJiwHWtDta52w7Cd7qyE", - "FJktmyjV9aBHEShT4hfnLpr9ELywdwVtEL1w1rSWgbVf", - )); - let response: RpcV2Response = json::from_value(response).unwrap(); - let response = response.result; - - assert!(response.is_valid); -} - -#[test] -fn test_disable_solana_platform_coin_with_tokens() { - let mm = _solana_supplied_node(); - block_on(enable_solana_with_tokens( - &mm, - "SOL-DEVNET", - &["USDC-SOL-DEVNET"], - "https://api.devnet.solana.com", - false, - )); - block_on(enable_spl(&mm, "ADEX-SOL-DEVNET")); - - // Try to passive platform coin, SOL-DEVNET. - let res = block_on(disable_coin(&mm, "SOL-DEVNET", false)); - assert!(res.passivized); - - // Try to disable ADEX-SOL-DEVNET and USDC-SOL-DEVNET - // This should work, because platform coin is still in the memory. - let res = block_on(disable_coin(&mm, "ADEX-SOL-DEVNET", false)); - assert!(!res.passivized); - let res = block_on(disable_coin(&mm, "USDC-SOL-DEVNET", false)); - assert!(!res.passivized); - - // Then try to force disable SOL-DEVNET platform coin. - let res = block_on(disable_coin(&mm, "SOL-DEVNET", true)); - assert!(!res.passivized); -} diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs index a090f64a50..304f6f4819 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -2,11 +2,11 @@ use crate::{generate_utxo_coin_with_random_privkey, MYCOIN, MYCOIN1}; use bitcrypto::dhash160; use coins::utxo::UtxoCommonOps; use coins::{ConfirmPaymentInput, DexFee, FundingTxSpend, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, - MakerCoinSwapOpsV2, MarketCoinOps, ParseCoinAssocTypes, RefundFundingSecretArgs, RefundMakerPaymentArgs, - RefundPaymentArgs, SendMakerPaymentArgs, SendTakerFundingArgs, SwapTxTypeWithSecretHash, - TakerCoinSwapOpsV2, Transaction, ValidateMakerPaymentArgs, ValidateTakerFundingArgs}; -use common::{block_on, now_sec, DEX_FEE_ADDR_RAW_PUBKEY}; -use futures01::Future; + MakerCoinSwapOpsV2, MarketCoinOps, ParseCoinAssocTypes, RefundFundingSecretArgs, + RefundMakerPaymentSecretArgs, RefundMakerPaymentTimelockArgs, RefundTakerPaymentArgs, + SendMakerPaymentArgs, SendTakerFundingArgs, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, Transaction, + ValidateMakerPaymentArgs, ValidateTakerFundingArgs}; +use common::{block_on, block_on_f01, now_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use mm2_number::MmNumber; use mm2_test_helpers::for_tests::{active_swaps, check_recent_swaps, coins_needed_for_kickstart, disable_coin, disable_coin_err, enable_native, get_locked_amount, mm_dump, my_swap_status, @@ -22,14 +22,16 @@ use uuid::Uuid; fn send_and_refund_taker_funding_timelock() { let (_mm_arc, coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); - let time_lock = now_sec() - 1000; + let funding_time_lock = now_sec() - 1000; let taker_secret_hash = &[0; 20]; let maker_pub = coin.my_public_key().unwrap(); let dex_fee = &DexFee::Standard("0.01".into()); let send_args = SendTakerFundingArgs { - time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, + maker_secret_hash: &[0; 20], maker_pub, dex_fee, premium_amount: "0.1".parse().unwrap(), @@ -53,9 +55,11 @@ fn send_and_refund_taker_funding_timelock() { let validate_args = ValidateTakerFundingArgs { funding_tx: &taker_funding_utxo_tx, - time_lock, + payment_time_lock: 0, + funding_time_lock, taker_secret_hash, - other_pub: maker_pub, + maker_secret_hash: &[], + taker_pub: maker_pub, dex_fee, premium_amount: "0.1".parse().unwrap(), trading_amount: 1.into(), @@ -63,16 +67,18 @@ fn send_and_refund_taker_funding_timelock() { }; block_on(coin.validate_taker_funding(validate_args)).unwrap(); - let refund_args = RefundPaymentArgs { + let refund_args = RefundTakerPaymentArgs { payment_tx: &serialize(&taker_funding_utxo_tx).take(), - time_lock, - other_pubkey: coin.my_public_key().unwrap(), + time_lock: funding_time_lock, + maker_pub: coin.my_public_key().unwrap(), tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerFunding { taker_secret_hash: &[0; 20], }, swap_unique_data: &[], - swap_contract_address: &None, watcher_reward: false, + dex_fee, + premium_amount: Default::default(), + trading_amount: Default::default(), }; let refund_tx = block_on(coin.refund_taker_funding_timelock(refund_args)).unwrap(); @@ -86,7 +92,7 @@ fn send_and_refund_taker_funding_timelock() { wait_until: now_sec() + 20, check_every: 1, }; - coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_input)).unwrap(); let found_refund_tx = block_on(coin.search_for_taker_funding_spend(&taker_funding_utxo_tx, 1, taker_secret_hash)).unwrap(); @@ -100,7 +106,7 @@ fn send_and_refund_taker_funding_timelock() { fn send_and_refund_taker_funding_secret() { let (_mm_arc, coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); - let time_lock = now_sec() - 1000; + let funding_time_lock = now_sec() - 1000; let taker_secret = [0; 32]; let taker_secret_hash_owned = dhash160(&taker_secret); let taker_secret_hash = taker_secret_hash_owned.as_slice(); @@ -108,8 +114,10 @@ fn send_and_refund_taker_funding_secret() { let dex_fee = &DexFee::Standard("0.01".into()); let send_args = SendTakerFundingArgs { - time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, + maker_secret_hash: &[0; 20], maker_pub, dex_fee, premium_amount: "0.1".parse().unwrap(), @@ -133,9 +141,11 @@ fn send_and_refund_taker_funding_secret() { let validate_args = ValidateTakerFundingArgs { funding_tx: &taker_funding_utxo_tx, - time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, - other_pub: maker_pub, + maker_secret_hash: &[], + taker_pub: maker_pub, dex_fee, premium_amount: "0.1".parse().unwrap(), trading_amount: 1.into(), @@ -145,12 +155,16 @@ fn send_and_refund_taker_funding_secret() { let refund_args = RefundFundingSecretArgs { funding_tx: &taker_funding_utxo_tx, - time_lock, + funding_time_lock, + payment_time_lock: 0, maker_pubkey: maker_pub, taker_secret: &taker_secret, taker_secret_hash, + maker_secret_hash: &[], + dex_fee, + premium_amount: "0.1".parse().unwrap(), + trading_amount: 1.into(), swap_unique_data: &[], - swap_contract_address: &None, watcher_reward: false, }; @@ -165,7 +179,7 @@ fn send_and_refund_taker_funding_secret() { wait_until: now_sec() + 20, check_every: 1, }; - coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_input)).unwrap(); let found_refund_tx = block_on(coin.search_for_taker_funding_spend(&taker_funding_utxo_tx, 1, taker_secret_hash)).unwrap(); @@ -192,8 +206,10 @@ fn send_and_spend_taker_funding() { let dex_fee = &DexFee::Standard("0.01".into()); let send_args = SendTakerFundingArgs { - time_lock: funding_time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, + maker_secret_hash: &[0; 20], maker_pub, dex_fee, premium_amount: "0.1".parse().unwrap(), @@ -217,9 +233,11 @@ fn send_and_spend_taker_funding() { let validate_args = ValidateTakerFundingArgs { funding_tx: &taker_funding_utxo_tx, - time_lock: funding_time_lock, + payment_time_lock: 0, + funding_time_lock, taker_secret_hash, - other_pub: taker_pub, + maker_secret_hash: &[], + taker_pub, dex_fee, premium_amount: "0.1".parse().unwrap(), trading_amount: 1.into(), @@ -249,7 +267,7 @@ fn send_and_spend_taker_funding() { wait_until: now_sec() + 20, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_input)).unwrap(); let found_spend_tx = block_on(taker_coin.search_for_taker_funding_spend(&taker_funding_utxo_tx, 1, taker_secret_hash)).unwrap(); @@ -279,8 +297,10 @@ fn send_and_spend_taker_payment_dex_fee_burn() { let dex_fee = &DexFee::with_burn("0.75".into(), "0.25".into()); let send_args = SendTakerFundingArgs { - time_lock: funding_time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, + maker_secret_hash, maker_pub, dex_fee, premium_amount: 0.into(), @@ -304,9 +324,11 @@ fn send_and_spend_taker_payment_dex_fee_burn() { let validate_args = ValidateTakerFundingArgs { funding_tx: &taker_funding_utxo_tx, - time_lock: funding_time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, - other_pub: taker_pub, + maker_secret_hash, + taker_pub, dex_fee, premium_amount: 0.into(), trading_amount: 777.into(), @@ -382,8 +404,10 @@ fn send_and_spend_taker_payment_standard_dex_fee() { let dex_fee = &DexFee::Standard(1.into()); let send_args = SendTakerFundingArgs { - time_lock: funding_time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, + maker_secret_hash, maker_pub, dex_fee, premium_amount: 0.into(), @@ -407,9 +431,11 @@ fn send_and_spend_taker_payment_standard_dex_fee() { let validate_args = ValidateTakerFundingArgs { funding_tx: &taker_funding_utxo_tx, - time_lock: funding_time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, - other_pub: taker_pub, + maker_secret_hash, + taker_pub, dex_fee, premium_amount: 0.into(), trading_amount: 777.into(), @@ -513,17 +539,17 @@ fn send_and_refund_maker_payment_timelock() { }; block_on(coin.validate_maker_payment_v2(validate_args)).unwrap(); - let refund_args = RefundPaymentArgs { + let refund_args = RefundMakerPaymentTimelockArgs { payment_tx: &serialize(&maker_payment).take(), time_lock, - other_pubkey: coin.my_public_key().unwrap(), + taker_pub: coin.my_public_key().unwrap(), tx_type_with_secret_hash: SwapTxTypeWithSecretHash::MakerPaymentV2 { taker_secret_hash, maker_secret_hash, }, swap_unique_data: &[], - swap_contract_address: &None, watcher_reward: false, + amount: Default::default(), }; let refund_tx = block_on(coin.refund_maker_payment_v2_timelock(refund_args)).unwrap(); @@ -577,7 +603,7 @@ fn send_and_refund_maker_payment_taker_secret() { }; block_on(coin.validate_maker_payment_v2(validate_args)).unwrap(); - let refund_args = RefundMakerPaymentArgs { + let refund_args = RefundMakerPaymentSecretArgs { maker_payment_tx: &maker_payment, time_lock, taker_secret_hash, @@ -585,6 +611,7 @@ fn send_and_refund_maker_payment_taker_secret() { swap_unique_data: &[], taker_secret, taker_pub, + amount: Default::default(), }; let refund_tx = block_on(coin.refund_maker_payment_v2_secret(refund_args)).unwrap(); diff --git a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs index 98973aaf96..43eb715324 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -13,12 +13,11 @@ use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, MmCoin INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_PAYMENT_STATE_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_REFUND_TX_ERR_LOG, INVALID_SCRIPT_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; -use common::{block_on, now_sec, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; +use common::{block_on, block_on_f01, now_sec, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::{key_pair_from_secret, key_pair_from_seed}; -use futures01::Future; -use mm2_main::mm2::lp_swap::{dex_fee_amount, dex_fee_amount_from_taker_coin, generate_secret, get_payment_locktime, - MAKER_PAYMENT_SENT_LOG, MAKER_PAYMENT_SPEND_FOUND_LOG, MAKER_PAYMENT_SPEND_SENT_LOG, - REFUND_TEST_FAILURE_LOG, TAKER_PAYMENT_REFUND_SENT_LOG, WATCHER_MESSAGE_SENT_LOG}; +use mm2_main::lp_swap::{dex_fee_amount, dex_fee_amount_from_taker_coin, generate_secret, get_payment_locktime, + MAKER_PAYMENT_SENT_LOG, MAKER_PAYMENT_SPEND_FOUND_LOG, MAKER_PAYMENT_SPEND_SENT_LOG, + REFUND_TEST_FAILURE_LOG, TAKER_PAYMENT_REFUND_SENT_LOG, WATCHER_MESSAGE_SENT_LOG}; use mm2_number::BigDecimal; use mm2_number::MmNumber; use mm2_test_helpers::for_tests::{enable_eth_coin, erc20_dev_conf, eth_dev_conf, eth_jst_testnet_conf, mm_dump, @@ -410,6 +409,7 @@ fn test_taker_saves_the_swap_as_successful_after_restart_panic_at_wait_for_taker "WatcherMessageSent", "TakerPaymentSpent", "MakerPaymentSpentByWatcher", + "MakerPaymentSpendConfirmed", "Finished", ]; check_actual_events(&mm_alice, &uuids[0], &expected_events); @@ -468,6 +468,7 @@ fn test_taker_saves_the_swap_as_successful_after_restart_panic_at_maker_payment_ "WatcherMessageSent", "TakerPaymentSpent", "MakerPaymentSpentByWatcher", + "MakerPaymentSpendConfirmed", "Finished", ]; check_actual_events(&mm_alice, &uuids[0], &expected_events); @@ -685,11 +686,8 @@ fn test_taker_completes_swap_after_taker_payment_spent_while_offline() { // stop taker after taker payment sent let taker_payment_msg = "Taker payment tx hash "; block_on(mm_alice.wait_for_log(120., |log| log.contains(taker_payment_msg))).unwrap(); - let alice_log = mm_alice.log_as_utf8().unwrap(); - let tx_hash_start = alice_log.find(taker_payment_msg).unwrap() + taker_payment_msg.len(); - let payment_tx_hash = alice_log[tx_hash_start..tx_hash_start + 64].to_string(); // ensure p2p message is sent to the maker, this happens before this message: - block_on(mm_alice.wait_for_log(120., |log| log.contains(&format!("Waiting for tx {}", payment_tx_hash)))).unwrap(); + block_on(mm_alice.wait_for_log(120., |log| log.contains("Waiting for maker to spend taker payment!"))).unwrap(); alice_conf.conf["dbdir"] = mm_alice.folder.join("DB").to_str().unwrap().into(); block_on(mm_alice.stop()).unwrap(); @@ -1208,15 +1206,13 @@ fn test_watcher_validate_taker_fee_utxo() { let taker_amount = MmNumber::from((10, 1)); let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, maker_coin.ticker(), &taker_amount); - let taker_fee = taker_coin - .send_taker_fee( - &DEX_FEE_ADDR_RAW_PUBKEY, - fee_amount, - Uuid::new_v4().as_bytes(), - lock_duration, - ) - .wait() - .unwrap(); + let taker_fee = block_on(taker_coin.send_taker_fee( + &DEX_FEE_ADDR_RAW_PUBKEY, + fee_amount, + Uuid::new_v4().as_bytes(), + lock_duration, + )) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_fee.tx_hex(), @@ -1226,30 +1222,26 @@ fn test_watcher_validate_taker_fee_utxo() { check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let validate_taker_fee_res = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: 0, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait(); + let validate_taker_fee_res = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: 0, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })); assert!(validate_taker_fee_res.is_ok()); - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: maker_coin.my_public_key().unwrap().to_vec(), - min_block_number: 0, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: maker_coin.my_public_key().unwrap().to_vec(), + min_block_number: 0, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { @@ -1259,17 +1251,15 @@ fn test_watcher_validate_taker_fee_utxo() { _ => panic!("Expected `WrongPaymentTx` invalid public key, found {:?}", error), } - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: std::u64::MAX, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: std::u64::MAX, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1281,17 +1271,15 @@ fn test_watcher_validate_taker_fee_utxo() { ), } - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: 0, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration: 0, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: 0, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration: 0, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1300,17 +1288,15 @@ fn test_watcher_validate_taker_fee_utxo() { _ => panic!("Expected `WrongPaymentTx` transaction too old, found {:?}", error), } - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: 0, - fee_addr: taker_pubkey.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: 0, + fee_addr: taker_pubkey.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1334,15 +1320,13 @@ fn test_watcher_validate_taker_fee_eth() { let taker_amount = MmNumber::from((1, 1)); let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, "ETH", &taker_amount); - let taker_fee = taker_coin - .send_taker_fee( - &DEX_FEE_ADDR_RAW_PUBKEY, - fee_amount, - Uuid::new_v4().as_bytes(), - lock_duration, - ) - .wait() - .unwrap(); + let taker_fee = block_on(taker_coin.send_taker_fee( + &DEX_FEE_ADDR_RAW_PUBKEY, + fee_amount, + Uuid::new_v4().as_bytes(), + lock_duration, + )) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_fee.tx_hex(), @@ -1351,31 +1335,27 @@ fn test_watcher_validate_taker_fee_eth() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); - - let validate_taker_fee_res = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: 0, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); + + let validate_taker_fee_res = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: 0, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })); assert!(validate_taker_fee_res.is_ok()); let wrong_keypair = key_pair_from_secret(random_secp256k1_secret().as_slice()).unwrap(); - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: wrong_keypair.public().to_vec(), - min_block_number: 0, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: wrong_keypair.public().to_vec(), + min_block_number: 0, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { @@ -1385,17 +1365,15 @@ fn test_watcher_validate_taker_fee_eth() { _ => panic!("Expected `WrongPaymentTx` invalid public key, found {:?}", error), } - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: std::u64::MAX, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: std::u64::MAX, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1407,17 +1385,15 @@ fn test_watcher_validate_taker_fee_eth() { ), } - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: 0, - fee_addr: taker_pubkey.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: 0, + fee_addr: taker_pubkey.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1441,15 +1417,13 @@ fn test_watcher_validate_taker_fee_erc20() { let taker_amount = MmNumber::from((1, 1)); let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, "ETH", &taker_amount); - let taker_fee = taker_coin - .send_taker_fee( - &DEX_FEE_ADDR_RAW_PUBKEY, - fee_amount, - Uuid::new_v4().as_bytes(), - lock_duration, - ) - .wait() - .unwrap(); + let taker_fee = block_on(taker_coin.send_taker_fee( + &DEX_FEE_ADDR_RAW_PUBKEY, + fee_amount, + Uuid::new_v4().as_bytes(), + lock_duration, + )) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_fee.tx_hex(), @@ -1458,31 +1432,27 @@ fn test_watcher_validate_taker_fee_erc20() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); - - let validate_taker_fee_res = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: 0, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); + + let validate_taker_fee_res = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: 0, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })); assert!(validate_taker_fee_res.is_ok()); let wrong_keypair = key_pair_from_secret(random_secp256k1_secret().as_slice()).unwrap(); - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: wrong_keypair.public().to_vec(), - min_block_number: 0, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: wrong_keypair.public().to_vec(), + min_block_number: 0, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { @@ -1492,17 +1462,15 @@ fn test_watcher_validate_taker_fee_erc20() { _ => panic!("Expected `WrongPaymentTx` invalid public key, found {:?}", error), } - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: std::u64::MAX, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: std::u64::MAX, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1514,17 +1482,15 @@ fn test_watcher_validate_taker_fee_erc20() { ), } - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: 0, - fee_addr: taker_pubkey.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: 0, + fee_addr: taker_pubkey.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1552,21 +1518,19 @@ fn test_watcher_validate_taker_payment_utxo() { let secret_hash = dhash160(&generate_secret().unwrap()); - let taker_payment = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pubkey, - secret_hash: secret_hash.as_slice(), - amount: BigDecimal::from(10), - swap_contract_address: &None, - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: None, - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pubkey, + secret_hash: secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &None, + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: None, + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment.tx_hex(), @@ -1575,21 +1539,19 @@ fn test_watcher_validate_taker_payment_utxo() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let taker_payment_refund_preimage = taker_coin - .create_taker_payment_refund_preimage( - &taker_payment.tx_hex(), - time_lock, - maker_pubkey, - secret_hash.as_slice(), - &None, - &[], - ) - .wait() - .unwrap(); - let validate_taker_payment_res = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { + let taker_payment_refund_preimage = block_on_f01(taker_coin.create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + maker_pubkey, + secret_hash.as_slice(), + &None, + &[], + )) + .unwrap(); + let validate_taker_payment_res = + block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), time_lock, @@ -1599,25 +1561,22 @@ fn test_watcher_validate_taker_payment_utxo() { wait_until: timeout, confirmations: 1, maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), - }) - .wait(); + })); assert!(validate_taker_payment_res.is_ok()); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), - time_lock, - taker_pub: maker_pubkey.to_vec(), - maker_pub: maker_pubkey.to_vec(), - secret_hash: secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), + time_lock, + taker_pub: maker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { @@ -1629,21 +1588,19 @@ fn test_watcher_validate_taker_payment_utxo() { // Used to get wrong swap id let wrong_secret_hash = dhash160(&generate_secret().unwrap()); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), - time_lock, - taker_pub: taker_pubkey.to_vec(), - maker_pub: maker_pubkey.to_vec(), - secret_hash: wrong_secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), + time_lock, + taker_pub: taker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { @@ -1656,21 +1613,19 @@ fn test_watcher_validate_taker_payment_utxo() { ), } - let taker_payment_wrong_secret = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pubkey, - secret_hash: wrong_secret_hash.as_slice(), - amount: BigDecimal::from(10), - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: None, - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment_wrong_secret = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pubkey, + secret_hash: wrong_secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: None, + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment_wrong_secret.tx_hex(), @@ -1679,23 +1634,21 @@ fn test_watcher_validate_taker_payment_utxo() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), - time_lock: 500, - taker_pub: taker_pubkey.to_vec(), - maker_pub: maker_pubkey.to_vec(), - secret_hash: wrong_secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), + time_lock: 500, + taker_pub: taker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { @@ -1708,33 +1661,29 @@ fn test_watcher_validate_taker_payment_utxo() { ), } - let wrong_taker_payment_refund_preimage = taker_coin - .create_taker_payment_refund_preimage( - &taker_payment.tx_hex(), - time_lock, - maker_pubkey, - wrong_secret_hash.as_slice(), - &None, - &[], - ) - .wait() - .unwrap(); + let wrong_taker_payment_refund_preimage = block_on_f01(taker_coin.create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + maker_pubkey, + wrong_secret_hash.as_slice(), + &None, + &[], + )) + .unwrap(); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: wrong_taker_payment_refund_preimage.tx_hex(), - time_lock, - taker_pub: taker_pubkey.to_vec(), - maker_pub: maker_pubkey.to_vec(), - secret_hash: secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: wrong_taker_payment_refund_preimage.tx_hex(), + time_lock, + taker_pub: taker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { @@ -1777,21 +1726,19 @@ fn test_watcher_validate_taker_payment_eth() { .unwrap(), ); - let taker_payment = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: secret_hash.as_slice(), - amount: taker_amount.clone(), - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: watcher_reward.clone(), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: taker_amount.clone(), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: watcher_reward.clone(), + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment.tx_hex(), @@ -1800,10 +1747,10 @@ fn test_watcher_validate_taker_payment_eth() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let validate_taker_payment_res = taker_coin - .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + let validate_taker_payment_res = block_on_f01(taker_coin.watcher_validate_taker_payment( + coins::WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), taker_payment_refund_preimage: Vec::new(), time_lock, @@ -1813,12 +1760,12 @@ fn test_watcher_validate_taker_payment_eth() { wait_until: timeout, confirmations: 1, maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait(); + }, + )); assert!(validate_taker_payment_res.is_ok()); - let error = taker_coin - .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + let error = block_on_f01( + taker_coin.watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), taker_payment_refund_preimage: Vec::new(), time_lock, @@ -1828,10 +1775,10 @@ fn test_watcher_validate_taker_payment_eth() { wait_until: timeout, confirmations: 1, maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + }), + ) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1843,24 +1790,22 @@ fn test_watcher_validate_taker_payment_eth() { ), } - let taker_payment_wrong_contract = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: secret_hash.as_slice(), - amount: taker_amount.clone(), - swap_contract_address: &Some("9130b257d37a52e52f21054c4da3450c72f595ce".into()), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: watcher_reward.clone(), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment_wrong_contract = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: taker_amount.clone(), + swap_contract_address: &Some("9130b257d37a52e52f21054c4da3450c72f595ce".into()), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: watcher_reward.clone(), + wait_for_confirmation_until, + })) + .unwrap(); - let error = taker_coin - .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + let error = block_on_f01( + taker_coin.watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { payment_tx: taker_payment_wrong_contract.tx_hex(), taker_payment_refund_preimage: Vec::new(), time_lock, @@ -1870,10 +1815,10 @@ fn test_watcher_validate_taker_payment_eth() { wait_until: timeout, confirmations: 1, maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + }), + ) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1887,8 +1832,8 @@ fn test_watcher_validate_taker_payment_eth() { // Used to get wrong swap id let wrong_secret_hash = dhash160(&generate_secret().unwrap()); - let error = taker_coin - .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + let error = block_on_f01( + taker_coin.watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), taker_payment_refund_preimage: Vec::new(), time_lock, @@ -1898,10 +1843,10 @@ fn test_watcher_validate_taker_payment_eth() { wait_until: timeout, confirmations: 1, maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + }), + ) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::UnexpectedPaymentState(err) => { @@ -1913,21 +1858,19 @@ fn test_watcher_validate_taker_payment_eth() { ), } - let taker_payment_wrong_secret = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: wrong_secret_hash.as_slice(), - amount: taker_amount, - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward, - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment_wrong_secret = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: wrong_secret_hash.as_slice(), + amount: taker_amount, + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment_wrong_secret.tx_hex(), @@ -1936,23 +1879,21 @@ fn test_watcher_validate_taker_payment_eth() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: Vec::new(), - time_lock, - taker_pub: taker_pub.to_vec(), - maker_pub: maker_pub.to_vec(), - secret_hash: wrong_secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1964,21 +1905,19 @@ fn test_watcher_validate_taker_payment_eth() { ), } - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: Vec::new(), - time_lock, - taker_pub: taker_pub.to_vec(), - maker_pub: taker_pub.to_vec(), - secret_hash: secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: taker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -2023,21 +1962,19 @@ fn test_watcher_validate_taker_payment_erc20() { .unwrap(), ); - let taker_payment = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: secret_hash.as_slice(), - amount: taker_amount.clone(), - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: watcher_reward.clone(), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: taker_amount.clone(), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: watcher_reward.clone(), + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment.tx_hex(), @@ -2046,10 +1983,10 @@ fn test_watcher_validate_taker_payment_erc20() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let validate_taker_payment_res = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { + let validate_taker_payment_res = + block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), taker_payment_refund_preimage: Vec::new(), time_lock, @@ -2059,25 +1996,22 @@ fn test_watcher_validate_taker_payment_erc20() { wait_until: timeout, confirmations: 1, maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait(); + })); assert!(validate_taker_payment_res.is_ok()); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: Vec::new(), - time_lock, - taker_pub: maker_pub.to_vec(), - maker_pub: maker_pub.to_vec(), - secret_hash: secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: maker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -2089,37 +2023,33 @@ fn test_watcher_validate_taker_payment_erc20() { ), } - let taker_payment_wrong_contract = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: secret_hash.as_slice(), - amount: taker_amount.clone(), - swap_contract_address: &Some("9130b257d37a52e52f21054c4da3450c72f595ce".into()), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: watcher_reward.clone(), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment_wrong_contract = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: taker_amount.clone(), + swap_contract_address: &Some("9130b257d37a52e52f21054c4da3450c72f595ce".into()), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: watcher_reward.clone(), + wait_for_confirmation_until, + })) + .unwrap(); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment_wrong_contract.tx_hex(), - taker_payment_refund_preimage: Vec::new(), - time_lock, - taker_pub: taker_pub.to_vec(), - maker_pub: maker_pub.to_vec(), - secret_hash: secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment_wrong_contract.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -2133,21 +2063,19 @@ fn test_watcher_validate_taker_payment_erc20() { // Used to get wrong swap id let wrong_secret_hash = dhash160(&generate_secret().unwrap()); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: Vec::new(), - time_lock, - taker_pub: taker_pub.to_vec(), - maker_pub: maker_pub.to_vec(), - secret_hash: wrong_secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::UnexpectedPaymentState(err) => { @@ -2159,21 +2087,19 @@ fn test_watcher_validate_taker_payment_erc20() { ), } - let taker_payment_wrong_secret = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: wrong_secret_hash.as_slice(), - amount: taker_amount, - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward, - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment_wrong_secret = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: wrong_secret_hash.as_slice(), + amount: taker_amount, + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment_wrong_secret.tx_hex(), @@ -2182,23 +2108,21 @@ fn test_watcher_validate_taker_payment_erc20() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: Vec::new(), - time_lock, - taker_pub: taker_pub.to_vec(), - maker_pub: maker_pub.to_vec(), - secret_hash: wrong_secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -2210,21 +2134,19 @@ fn test_watcher_validate_taker_payment_erc20() { ), } - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: Vec::new(), - time_lock, - taker_pub: taker_pub.to_vec(), - maker_pub: taker_pub.to_vec(), - secret_hash: secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: taker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -2250,21 +2172,19 @@ fn test_taker_validates_taker_payment_refund_utxo() { let secret_hash = dhash160(&generate_secret().unwrap()); - let taker_payment = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pubkey, - secret_hash: secret_hash.as_slice(), - amount: BigDecimal::from(10), - swap_contract_address: &None, - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: None, - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pubkey, + secret_hash: secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &None, + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: None, + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment.tx_hex(), @@ -2273,34 +2193,30 @@ fn test_taker_validates_taker_payment_refund_utxo() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let taker_payment_refund_preimage = taker_coin - .create_taker_payment_refund_preimage( - &taker_payment.tx_hex(), - time_lock, - maker_pubkey, - secret_hash.as_slice(), - &None, - &[], - ) - .wait() - .unwrap(); + let taker_payment_refund_preimage = block_on_f01(taker_coin.create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + maker_pubkey, + secret_hash.as_slice(), + &None, + &[], + )) + .unwrap(); - let taker_payment_refund = taker_coin - .send_taker_payment_refund_preimage(RefundPaymentArgs { - payment_tx: &taker_payment_refund_preimage.tx_hex(), - other_pubkey: maker_pubkey, - tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { - maker_secret_hash: secret_hash.as_slice(), - }, - time_lock, - swap_contract_address: &None, - swap_unique_data: &[], - watcher_reward: false, - }) - .wait() - .unwrap(); + let taker_payment_refund = block_on_f01(taker_coin.send_taker_payment_refund_preimage(RefundPaymentArgs { + payment_tx: &taker_payment_refund_preimage.tx_hex(), + other_pubkey: maker_pubkey, + tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { + maker_secret_hash: secret_hash.as_slice(), + }, + time_lock, + swap_contract_address: &None, + swap_unique_data: &[], + watcher_reward: false, + })) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: taker_payment_refund.tx_hex(), @@ -2313,9 +2229,7 @@ fn test_taker_validates_taker_payment_refund_utxo() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let validate_watcher_refund = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait(); + let validate_watcher_refund = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)); assert!(validate_watcher_refund.is_ok()); } @@ -2347,21 +2261,19 @@ fn test_taker_validates_taker_payment_refund_eth() { )) .unwrap(); - let taker_payment = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: secret_hash.as_slice(), - amount: taker_amount.clone(), - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: Some(watcher_reward.clone()), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: taker_amount.clone(), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: Some(watcher_reward.clone()), + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment.tx_hex(), @@ -2370,19 +2282,17 @@ fn test_taker_validates_taker_payment_refund_eth() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let taker_payment_refund_preimage = taker_coin - .create_taker_payment_refund_preimage( - &taker_payment.tx_hex(), - time_lock, - taker_pub, - secret_hash.as_slice(), - &taker_coin.swap_contract_address(), - &[], - ) - .wait() - .unwrap(); + let taker_payment_refund_preimage = block_on_f01(taker_coin.create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + taker_pub, + secret_hash.as_slice(), + &taker_coin.swap_contract_address(), + &[], + )) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: taker_payment_refund_preimage.tx_hex(), @@ -2395,9 +2305,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2411,20 +2319,18 @@ fn test_taker_validates_taker_payment_refund_eth() { ), } - let taker_payment_refund = taker_coin - .send_taker_payment_refund_preimage(RefundPaymentArgs { - payment_tx: &taker_payment_refund_preimage.tx_hex(), - other_pubkey: taker_pub, - tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { - maker_secret_hash: secret_hash.as_slice(), - }, - time_lock, - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - watcher_reward: true, - }) - .wait() - .unwrap(); + let taker_payment_refund = block_on_f01(taker_coin.send_taker_payment_refund_preimage(RefundPaymentArgs { + payment_tx: &taker_payment_refund_preimage.tx_hex(), + other_pubkey: taker_pub, + tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { + maker_secret_hash: secret_hash.as_slice(), + }, + time_lock, + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + watcher_reward: true, + })) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: taker_payment_refund.tx_hex(), @@ -2437,9 +2343,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let validate_watcher_refund = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait(); + let validate_watcher_refund = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)); assert!(validate_watcher_refund.is_ok()); let validate_input = ValidateWatcherSpendInput { @@ -2452,9 +2356,7 @@ fn test_taker_validates_taker_payment_refund_eth() { watcher_reward: Some(watcher_reward.clone()), spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2479,9 +2381,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = maker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(maker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2506,9 +2406,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2536,9 +2434,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2566,9 +2462,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2596,9 +2490,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2623,9 +2515,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2672,21 +2562,19 @@ fn test_taker_validates_taker_payment_refund_erc20() { .unwrap(), ); - let taker_payment = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: secret_hash.as_slice(), - amount: taker_amount.clone(), - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: watcher_reward.clone(), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: taker_amount.clone(), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: watcher_reward.clone(), + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment.tx_hex(), @@ -2695,34 +2583,30 @@ fn test_taker_validates_taker_payment_refund_erc20() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let taker_payment_refund_preimage = taker_coin - .create_taker_payment_refund_preimage( - &taker_payment.tx_hex(), - time_lock, - taker_pub, - secret_hash.as_slice(), - &taker_coin.swap_contract_address(), - &[], - ) - .wait() - .unwrap(); + let taker_payment_refund_preimage = block_on_f01(taker_coin.create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + taker_pub, + secret_hash.as_slice(), + &taker_coin.swap_contract_address(), + &[], + )) + .unwrap(); - let taker_payment_refund = taker_coin - .send_taker_payment_refund_preimage(RefundPaymentArgs { - payment_tx: &taker_payment_refund_preimage.tx_hex(), - other_pubkey: taker_pub, - tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { - maker_secret_hash: secret_hash.as_slice(), - }, - time_lock, - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - watcher_reward: true, - }) - .wait() - .unwrap(); + let taker_payment_refund = block_on_f01(taker_coin.send_taker_payment_refund_preimage(RefundPaymentArgs { + payment_tx: &taker_payment_refund_preimage.tx_hex(), + other_pubkey: taker_pub, + tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { + maker_secret_hash: secret_hash.as_slice(), + }, + time_lock, + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + watcher_reward: true, + })) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: taker_payment_refund.tx_hex(), @@ -2735,9 +2619,7 @@ fn test_taker_validates_taker_payment_refund_erc20() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let validate_watcher_refund = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait(); + let validate_watcher_refund = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)); assert!(validate_watcher_refund.is_ok()); let validate_input = ValidateWatcherSpendInput { @@ -2751,9 +2633,7 @@ fn test_taker_validates_taker_payment_refund_erc20() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2783,54 +2663,48 @@ fn test_taker_validates_maker_payment_spend_utxo() { let secret = generate_secret().unwrap(); let secret_hash = dhash160(&secret); - let maker_payment = maker_coin - .send_maker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: taker_pubkey, - secret_hash: secret_hash.as_slice(), - amount: BigDecimal::from(10), - swap_contract_address: &None, - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: None, - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let maker_payment = block_on(maker_coin.send_maker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: taker_pubkey, + secret_hash: secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &None, + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: None, + wait_for_confirmation_until, + })) + .unwrap(); - maker_coin - .wait_for_confirmations(ConfirmPaymentInput { - payment_tx: maker_payment.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: timeout, - check_every: 1, - }) - .wait() - .unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(ConfirmPaymentInput { + payment_tx: maker_payment.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + })) + .unwrap(); - let maker_payment_spend_preimage = taker_coin - .create_maker_payment_spend_preimage( - &maker_payment.tx_hex(), - time_lock, - maker_pubkey, - secret_hash.as_slice(), - &[], - ) - .wait() - .unwrap(); + let maker_payment_spend_preimage = block_on_f01(taker_coin.create_maker_payment_spend_preimage( + &maker_payment.tx_hex(), + time_lock, + maker_pubkey, + secret_hash.as_slice(), + &[], + )) + .unwrap(); - let maker_payment_spend = taker_coin - .send_maker_payment_spend_preimage(SendMakerPaymentSpendPreimageInput { + let maker_payment_spend = block_on_f01(taker_coin.send_maker_payment_spend_preimage( + SendMakerPaymentSpendPreimageInput { preimage: &maker_payment_spend_preimage.tx_hex(), secret_hash: secret_hash.as_slice(), secret: secret.as_slice(), taker_pub: taker_pubkey, watcher_reward: false, - }) - .wait() - .unwrap(); + }, + )) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: maker_payment_spend.tx_hex(), @@ -2843,9 +2717,7 @@ fn test_taker_validates_maker_payment_spend_utxo() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let validate_watcher_spend = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait(); + let validate_watcher_spend = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)); assert!(validate_watcher_spend.is_ok()); } @@ -2877,43 +2749,37 @@ fn test_taker_validates_maker_payment_spend_eth() { .unwrap() .unwrap(); - let maker_payment = maker_coin - .send_maker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: taker_pub, - secret_hash: secret_hash.as_slice(), - amount: maker_amount.clone(), - swap_contract_address: &maker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: Some(watcher_reward.clone()), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let maker_payment = block_on(maker_coin.send_maker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: taker_pub, + secret_hash: secret_hash.as_slice(), + amount: maker_amount.clone(), + swap_contract_address: &maker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: Some(watcher_reward.clone()), + wait_for_confirmation_until, + })) + .unwrap(); - maker_coin - .wait_for_confirmations(ConfirmPaymentInput { - payment_tx: maker_payment.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: timeout, - check_every: 1, - }) - .wait() - .unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(ConfirmPaymentInput { + payment_tx: maker_payment.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + })) + .unwrap(); - let maker_payment_spend_preimage = taker_coin - .create_maker_payment_spend_preimage( - &maker_payment.tx_hex(), - time_lock, - maker_pub, - secret_hash.as_slice(), - &[], - ) - .wait() - .unwrap(); + let maker_payment_spend_preimage = block_on_f01(taker_coin.create_maker_payment_spend_preimage( + &maker_payment.tx_hex(), + time_lock, + maker_pub, + secret_hash.as_slice(), + &[], + )) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: maker_payment_spend_preimage.tx_hex(), @@ -2926,9 +2792,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2942,27 +2806,25 @@ fn test_taker_validates_maker_payment_spend_eth() { ), } - let maker_payment_spend = taker_coin - .send_maker_payment_spend_preimage(SendMakerPaymentSpendPreimageInput { + let maker_payment_spend = block_on_f01(taker_coin.send_maker_payment_spend_preimage( + SendMakerPaymentSpendPreimageInput { preimage: &maker_payment_spend_preimage.tx_hex(), secret_hash: secret_hash.as_slice(), secret: secret.as_slice(), taker_pub, watcher_reward: true, - }) - .wait() - .unwrap(); + }, + )) + .unwrap(); - maker_coin - .wait_for_confirmations(ConfirmPaymentInput { - payment_tx: maker_payment_spend.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: timeout, - check_every: 1, - }) - .wait() - .unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(ConfirmPaymentInput { + payment_tx: maker_payment_spend.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + })) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: maker_payment_spend.tx_hex(), @@ -2975,10 +2837,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() - .unwrap(); + block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)).unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: maker_payment_spend.tx_hex(), @@ -2991,9 +2850,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3018,9 +2875,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3045,9 +2900,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = maker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(maker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3075,9 +2928,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3105,9 +2956,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3135,9 +2984,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3162,9 +3009,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3206,65 +3051,57 @@ fn test_taker_validates_maker_payment_spend_erc20() { )) .unwrap(); - let maker_payment = maker_coin - .send_maker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: taker_pub, - secret_hash: secret_hash.as_slice(), - amount: maker_amount.clone(), - swap_contract_address: &maker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: watcher_reward.clone(), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let maker_payment = block_on(maker_coin.send_maker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: taker_pub, + secret_hash: secret_hash.as_slice(), + amount: maker_amount.clone(), + swap_contract_address: &maker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: watcher_reward.clone(), + wait_for_confirmation_until, + })) + .unwrap(); - maker_coin - .wait_for_confirmations(ConfirmPaymentInput { - payment_tx: maker_payment.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: timeout, - check_every: 1, - }) - .wait() - .unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(ConfirmPaymentInput { + payment_tx: maker_payment.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + })) + .unwrap(); - let maker_payment_spend_preimage = taker_coin - .create_maker_payment_spend_preimage( - &maker_payment.tx_hex(), - time_lock, - maker_pub, - secret_hash.as_slice(), - &[], - ) - .wait() - .unwrap(); + let maker_payment_spend_preimage = block_on_f01(taker_coin.create_maker_payment_spend_preimage( + &maker_payment.tx_hex(), + time_lock, + maker_pub, + secret_hash.as_slice(), + &[], + )) + .unwrap(); - let maker_payment_spend = taker_coin - .send_maker_payment_spend_preimage(SendMakerPaymentSpendPreimageInput { + let maker_payment_spend = block_on_f01(taker_coin.send_maker_payment_spend_preimage( + SendMakerPaymentSpendPreimageInput { preimage: &maker_payment_spend_preimage.tx_hex(), secret_hash: secret_hash.as_slice(), secret: secret.as_slice(), taker_pub, watcher_reward: true, - }) - .wait() - .unwrap(); + }, + )) + .unwrap(); - maker_coin - .wait_for_confirmations(ConfirmPaymentInput { - payment_tx: maker_payment_spend.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: timeout, - check_every: 1, - }) - .wait() - .unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(ConfirmPaymentInput { + payment_tx: maker_payment_spend.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + })) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: maker_payment_spend.tx_hex(), @@ -3277,10 +3114,7 @@ fn test_taker_validates_maker_payment_spend_erc20() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() - .unwrap(); + block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)).unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: maker_payment_spend.tx_hex(), @@ -3293,9 +3127,7 @@ fn test_taker_validates_maker_payment_spend_erc20() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3329,7 +3161,7 @@ fn test_send_taker_payment_refund_preimage_utxo() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); + let tx = block_on(coin.send_taker_payment(taker_payment_args)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx.tx_hex(), @@ -3338,27 +3170,30 @@ fn test_send_taker_payment_refund_preimage_utxo() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let refund_tx = coin - .create_taker_payment_refund_preimage(&tx.tx_hex(), time_lock, my_public_key, &[0; 20], &None, &[]) - .wait() - .unwrap(); + let refund_tx = block_on_f01(coin.create_taker_payment_refund_preimage( + &tx.tx_hex(), + time_lock, + my_public_key, + &[0; 20], + &None, + &[], + )) + .unwrap(); - let refund_tx = coin - .send_taker_payment_refund_preimage(RefundPaymentArgs { - payment_tx: &refund_tx.tx_hex(), - swap_contract_address: &None, - tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { - maker_secret_hash: &[0; 20], - }, - other_pubkey: my_public_key, - time_lock, - swap_unique_data: &[], - watcher_reward: false, - }) - .wait() - .unwrap(); + let refund_tx = block_on_f01(coin.send_taker_payment_refund_preimage(RefundPaymentArgs { + payment_tx: &refund_tx.tx_hex(), + swap_contract_address: &None, + tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { + maker_secret_hash: &[0; 20], + }, + other_pubkey: my_public_key, + time_lock, + swap_unique_data: &[], + watcher_reward: false, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: refund_tx.tx_hex(), @@ -3367,7 +3202,7 @@ fn test_send_taker_payment_refund_preimage_utxo() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -3465,5 +3300,5 @@ fn test_watcher_reward() { let watcher_reward = block_on(utxo_coin.get_maker_watcher_reward(&MmCoinEnum::UtxoCoin(utxo_coin.clone()), None, timeout)).unwrap(); - assert!(matches!(watcher_reward, None)); + assert!(watcher_reward.is_none()); } diff --git a/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs b/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs index 13c35cb0be..31c6c264e3 100644 --- a/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs @@ -1,7 +1,7 @@ use crate::generate_utxo_coin_with_random_privkey; use crate::integration_tests_common::enable_native; use common::block_on; -use mm2_main::mm2::lp_swap::get_payment_locktime; +use mm2_main::lp_swap::get_payment_locktime; use mm2_rpc::data::legacy::OrderConfirmationsSettings; use mm2_test_helpers::for_tests::{mm_dump, MarketMakerIt}; use serde_json::Value as Json; diff --git a/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs b/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs index 8ca965e784..785eb0a849 100644 --- a/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs @@ -13,8 +13,8 @@ use std::time::Duration; const UNFINISHED_MAKER_SWAP: &str = r#"{"type":"Maker","uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","events":[{"timestamp":1588244036250,"event":{"type":"Started","data":{"taker_coin":"MYCOIN1","maker_coin":"MYCOIN","taker":"859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521","secret":"dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498","secret_hash":"9304bce3196f344b2a22dc99db406e95ab6f3107","my_persistent_pub":"02987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","lock_duration":7800,"maker_amount":"999.99999","taker_amount":"999.99999","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":1,"taker_payment_requires_nota":false,"maker_payment_lock":1588259636,"uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","started_at":1588244036,"maker_coin_start_block":12,"taker_coin_start_block":11}}},{"timestamp":1588244038035,"event":{"type":"Negotiated","data":{"taker_payment_locktime":1588251836,"taker_pubkey":"02859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521"}}}],"maker_amount":"999.99999","maker_coin":"MYCOIN","taker_amount":"999.99999","taker_coin":"MYCOIN1","gui":"nogui","mm_version":"UNKNOWN","success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","MakerPaymentWaitConfirmFailed","TakerPaymentValidateFailed","TakerPaymentWaitConfirmFailed","TakerPaymentSpendFailed","MakerPaymentWaitRefundStarted","MakerPaymentRefunded","MakerPaymentRefundFailed"]}"#; const FINISHED_MAKER_SWAP: &str = r#"{"type":"Maker","uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","events":[{"timestamp":1588244036250,"event":{"type":"Started","data":{"taker_coin":"MYCOIN1","maker_coin":"MYCOIN","taker":"859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521","secret":"dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498","secret_hash":"9304bce3196f344b2a22dc99db406e95ab6f3107","my_persistent_pub":"02987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","lock_duration":7800,"maker_amount":"999.99999","taker_amount":"999.99999","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":1,"taker_payment_requires_nota":false,"maker_payment_lock":1588259636,"uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","started_at":1588244036,"maker_coin_start_block":12,"taker_coin_start_block":11}}},{"timestamp":1588244038035,"event":{"type":"Negotiated","data":{"taker_payment_locktime":1588251836,"taker_pubkey":"02859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521"}}},{"timestamp":1588244038463,"event":{"type":"TakerFeeValidated","data":{"tx_hex":"0400008085202f8901bdde9bca02870787441f6068e4c2a869a3aac3d1d0925f6a6e27874343544d0a010000006a47304402206694a794693b55fbe8205cb1cbb992d92fa2a9a851763ad1bf1628c16deaf73e02203cfff465504bdd6c51e8fbd45dd2e1187142fdc29e82f36f577616d9d6097d7a012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff02dfceab07000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac39fd41892e0000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac46aeaa5e000000000000000000000000000000","tx_hash":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf","RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"total_amount":"2000","spent_by_me":"0","received_by_me":"0","my_balance_change":"0","block_height":13,"timestamp":1588244038,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6"}}},{"timestamp":1588244038505,"event":{"type":"MakerPaymentSent","data":{"tx_hex":"0400008085202f890163cc0aceb3b432f84c1407991a0389b74b7842f030aa261203aa0b4b9a9a15fd010000006b483045022100f3239794b7b0e1c75aae084a65535270f154231ce86a4739976ff69eeff1ebd402206c0aeb28b7b4b2e77e98b3c3efd9a20d85c2cd0627acc3f45d577c0e88c34fb4012102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dffffffff0118e476481700000017a914dc4ed4686174503b85374bfb0aefe07a9fb37bcf8746aeaa5e000000000000000000000000000000","tx_hash":"9a4c0b3f85ed0bb24dc9575ce7c2fd6bc50ad9d37c91c478946f9c33d15abfdf","from":["RAzSdYQhjCFdyhjrBz1AZQDVg3Hu8DrzYc"],"to":["bYp9ncp3V7FYsymipriVbdd3QL72hK9hio"],"total_amount":"1000","spent_by_me":"1000","received_by_me":"0","my_balance_change":"-1000","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN","internal_id":"9a4c0b3f85ed0bb24dc9575ce7c2fd6bc50ad9d37c91c478946f9c33d15abfdf"}}},{"timestamp":1588244053938,"event":{"type":"TakerPaymentReceived","data":{"tx_hex":"0400008085202f8901c6272ed6f0900c449a6a6f948d74f85688228a843a672661ddbf1c4d48d71871010000006b483045022100c37627385c66b7bdf466b4dd4e7095b7551e6f5ea35f9bcc6344eb629f2edcb202203280eaba64b4d72010500166fab62cf34a687a516b2fe83d4eceaf8572cb37a7012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff0218e476481700000017a91431ff75ed72cd135a7ce50d121e71efc37066b9f9873915cb40170000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac55aeaa5e000000000000000000000000000000","tx_hash":"b4718ce94aa43f9073ab0b70c18ab4ea4b587338fb7110f20ff7d1bb452df08f","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC","bHHdqM8XWDHee2oyzydwUJKEE2BjofEtZH"],"total_amount":"1998.71298873","spent_by_me":"0","received_by_me":"0","my_balance_change":"0","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"b4718ce94aa43f9073ab0b70c18ab4ea4b587338fb7110f20ff7d1bb452df08f"}}},{"timestamp":1588244053939,"event":{"type":"TakerPaymentWaitConfirmStarted"}},{"timestamp":1588244068951,"event":{"type":"TakerPaymentValidatedAndConfirmed"}},{"timestamp":1588244068959,"event":{"type":"TakerPaymentSpent","data":{"tx_hex":"0400008085202f89018ff02d45bbd1f70ff21071fb3873584beab48ac1700bab73903fa44ae98c71b400000000d747304402204f0d641a3916e54d6788744c3229110a431eff18634c66fbd1741f9ca7dba99d02202315ee1d9317cc4d5d75d01f066f2c8a59876f790106d310144cdc03c25f985e0120dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498004c6b6304bcccaa5eb1752102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ac6782012088a9149304bce3196f344b2a22dc99db406e95ab6f3107882102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dac68ffffffff0130e07648170000001976a91412c553e8469363f2d30268c475af1e9186cc90af88ac54a0aa5e000000000000000000000000000000","tx_hash":"e7aed7a77e47b44dc9d12166589bbade70faea10b64888f73ed4be04bcc9f9a9","from":["bHHdqM8XWDHee2oyzydwUJKEE2BjofEtZH"],"to":["RAzSdYQhjCFdyhjrBz1AZQDVg3Hu8DrzYc"],"total_amount":"999.99999","spent_by_me":"0","received_by_me":"999.99998","my_balance_change":"999.99998","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"e7aed7a77e47b44dc9d12166589bbade70faea10b64888f73ed4be04bcc9f9a9"}}},{"timestamp":1588244068960,"event":{"type":"Finished"}}],"maker_amount":"999.99999","maker_coin":"MYCOIN","taker_amount":"999.99999","taker_coin":"MYCOIN1","gui":"nogui","mm_version":"UNKNOWN","success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","MakerPaymentWaitConfirmFailed","TakerPaymentValidateFailed","TakerPaymentWaitConfirmFailed","TakerPaymentSpendFailed","MakerPaymentWaitRefundStarted","MakerPaymentRefunded","MakerPaymentRefundFailed"]}"#; -const UNFINISHED_TAKER_SWAP: &str = r#"{"type":"Taker","uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","events":[{"timestamp":1588244036253,"event":{"type":"Started","data":{"taker_coin":"MYCOIN1","maker_coin":"MYCOIN","maker":"987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","my_persistent_pub":"02859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521","lock_duration":7800,"maker_amount":"999.99999","taker_amount":"999.99999","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":1,"taker_payment_requires_nota":false,"taker_payment_lock":1588251836,"uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","started_at":1588244036,"maker_payment_wait":1588247156,"maker_coin_start_block":12,"taker_coin_start_block":11}}},{"timestamp":1588244038239,"event":{"type":"Negotiated","data":{"maker_payment_locktime":1588259636,"maker_pubkey":"02987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","secret_hash":"9304bce3196f344b2a22dc99db406e95ab6f3107"}}},{"timestamp":1588244038271,"event":{"type":"TakerFeeSent","data":{"tx_hex":"0400008085202f8901bdde9bca02870787441f6068e4c2a869a3aac3d1d0925f6a6e27874343544d0a010000006a47304402206694a794693b55fbe8205cb1cbb992d92fa2a9a851763ad1bf1628c16deaf73e02203cfff465504bdd6c51e8fbd45dd2e1187142fdc29e82f36f577616d9d6097d7a012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff02dfceab07000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac39fd41892e0000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac46aeaa5e000000000000000000000000000000","tx_hash":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf","RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"total_amount":"2000","spent_by_me":"2000","received_by_me":"1998.71298873","my_balance_change":"-1.28701127","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6"}}}],"maker_amount":"999.99999","maker_coin":"MYCOIN","taker_amount":"999.99999","taker_coin":"MYCOIN1","gui":"nogui","mm_version":"UNKNOWN","success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"]}"#; -const FINISHED_TAKER_SWAP: &str = r#"{"type":"Taker","uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","events":[{"timestamp":1588244036253,"event":{"type":"Started","data":{"taker_coin":"MYCOIN1","maker_coin":"MYCOIN","maker":"987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","my_persistent_pub":"02859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521","lock_duration":7800,"maker_amount":"999.99999","taker_amount":"999.99999","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":1,"taker_payment_requires_nota":false,"taker_payment_lock":1588251836,"uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","started_at":1588244036,"maker_payment_wait":1588247156,"maker_coin_start_block":12,"taker_coin_start_block":11}}},{"timestamp":1588244038239,"event":{"type":"Negotiated","data":{"maker_payment_locktime":1588259636,"maker_pubkey":"02987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","secret_hash":"9304bce3196f344b2a22dc99db406e95ab6f3107"}}},{"timestamp":1588244038271,"event":{"type":"TakerFeeSent","data":{"tx_hex":"0400008085202f8901bdde9bca02870787441f6068e4c2a869a3aac3d1d0925f6a6e27874343544d0a010000006a47304402206694a794693b55fbe8205cb1cbb992d92fa2a9a851763ad1bf1628c16deaf73e02203cfff465504bdd6c51e8fbd45dd2e1187142fdc29e82f36f577616d9d6097d7a012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff02dfceab07000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac39fd41892e0000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac46aeaa5e000000000000000000000000000000","tx_hash":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf","RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"total_amount":"2000","spent_by_me":"2000","received_by_me":"1998.71298873","my_balance_change":"-1.28701127","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6"}}},{"timestamp":1588244038698,"event":{"type":"MakerPaymentReceived","data":{"tx_hex":"0400008085202f890163cc0aceb3b432f84c1407991a0389b74b7842f030aa261203aa0b4b9a9a15fd010000006b483045022100f3239794b7b0e1c75aae084a65535270f154231ce86a4739976ff69eeff1ebd402206c0aeb28b7b4b2e77e98b3c3efd9a20d85c2cd0627acc3f45d577c0e88c34fb4012102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dffffffff0118e476481700000017a914dc4ed4686174503b85374bfb0aefe07a9fb37bcf8746aeaa5e000000000000000000000000000000","tx_hash":"9a4c0b3f85ed0bb24dc9575ce7c2fd6bc50ad9d37c91c478946f9c33d15abfdf","from":["RAzSdYQhjCFdyhjrBz1AZQDVg3Hu8DrzYc"],"to":["bYp9ncp3V7FYsymipriVbdd3QL72hK9hio"],"total_amount":"1000","spent_by_me":"0","received_by_me":"0","my_balance_change":"0","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN","internal_id":"9a4c0b3f85ed0bb24dc9575ce7c2fd6bc50ad9d37c91c478946f9c33d15abfdf"}}},{"timestamp":1588244038699,"event":{"type":"MakerPaymentWaitConfirmStarted"}},{"timestamp":1588244053712,"event":{"type":"MakerPaymentValidatedAndConfirmed"}},{"timestamp":1588244053745,"event":{"type":"TakerPaymentSent","data":{"tx_hex":"0400008085202f8901c6272ed6f0900c449a6a6f948d74f85688228a843a672661ddbf1c4d48d71871010000006b483045022100c37627385c66b7bdf466b4dd4e7095b7551e6f5ea35f9bcc6344eb629f2edcb202203280eaba64b4d72010500166fab62cf34a687a516b2fe83d4eceaf8572cb37a7012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff0218e476481700000017a91431ff75ed72cd135a7ce50d121e71efc37066b9f9873915cb40170000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac55aeaa5e000000000000000000000000000000","tx_hash":"b4718ce94aa43f9073ab0b70c18ab4ea4b587338fb7110f20ff7d1bb452df08f","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC","bHHdqM8XWDHee2oyzydwUJKEE2BjofEtZH"],"total_amount":"1998.71298873","spent_by_me":"1998.71298873","received_by_me":"998.71298873","my_balance_change":"-1000","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"b4718ce94aa43f9073ab0b70c18ab4ea4b587338fb7110f20ff7d1bb452df08f"}}},{"timestamp":1588244078860,"event":{"type":"TakerPaymentSpent","data":{"transaction":{"tx_hex":"0400008085202f89018ff02d45bbd1f70ff21071fb3873584beab48ac1700bab73903fa44ae98c71b400000000d747304402204f0d641a3916e54d6788744c3229110a431eff18634c66fbd1741f9ca7dba99d02202315ee1d9317cc4d5d75d01f066f2c8a59876f790106d310144cdc03c25f985e0120dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498004c6b6304bcccaa5eb1752102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ac6782012088a9149304bce3196f344b2a22dc99db406e95ab6f3107882102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dac68ffffffff0130e07648170000001976a91412c553e8469363f2d30268c475af1e9186cc90af88ac54a0aa5e000000000000000000000000000000","tx_hash":"e7aed7a77e47b44dc9d12166589bbade70faea10b64888f73ed4be04bcc9f9a9","from":["bHHdqM8XWDHee2oyzydwUJKEE2BjofEtZH"],"to":["RAzSdYQhjCFdyhjrBz1AZQDVg3Hu8DrzYc"],"total_amount":"999.99999","spent_by_me":"0","received_by_me":"0","my_balance_change":"0","block_height":29,"timestamp":1588244070,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"e7aed7a77e47b44dc9d12166589bbade70faea10b64888f73ed4be04bcc9f9a9"},"secret":"dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498"}}},{"timestamp":1588244078870,"event":{"type":"MakerPaymentSpent","data":{"tx_hex":"0400008085202f8901dfbf5ad1339c6f9478c4917cd3d90ac56bfdc2e75c57c94db20bed853f0b4c9a00000000d848304502210092535c081325ba5261699d7cfd4c503fb6125dde86389b83f40f3e2c006039bb022063cfd72aa15558dee874cac08b22dbcf11d3f06c8e48b0ddaf75b86887d604410120dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498004c6b630434ebaa5eb1752102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dac6782012088a9149304bce3196f344b2a22dc99db406e95ab6f3107882102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ac68ffffffff0130e07648170000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac5ea0aa5e000000000000000000000000000000","tx_hash":"caea128b1c85a88abd5924e512780ee18952dadc217b0c06f4b2820eb71d03bc","from":["bYp9ncp3V7FYsymipriVbdd3QL72hK9hio"],"to":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"total_amount":"999.99999","spent_by_me":"0","received_by_me":"999.99998","my_balance_change":"999.99998","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN","internal_id":"caea128b1c85a88abd5924e512780ee18952dadc217b0c06f4b2820eb71d03bc"}}},{"timestamp":1588244078871,"event":{"type":"Finished"}}],"maker_amount":"999.99999","maker_coin":"MYCOIN","taker_amount":"999.99999","taker_coin":"MYCOIN1","gui":"nogui","mm_version":"UNKNOWN","success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"]}"#; +const UNFINISHED_TAKER_SWAP: &str = r#"{"type":"Taker","uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","events":[{"timestamp":1588244036253,"event":{"type":"Started","data":{"taker_coin":"MYCOIN1","maker_coin":"MYCOIN","maker":"987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","my_persistent_pub":"02859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521","lock_duration":7800,"maker_amount":"999.99999","taker_amount":"999.99999","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":1,"taker_payment_requires_nota":false,"taker_payment_lock":1588251836,"uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","started_at":1588244036,"maker_payment_wait":1588247156,"maker_coin_start_block":12,"taker_coin_start_block":11}}},{"timestamp":1588244038239,"event":{"type":"Negotiated","data":{"maker_payment_locktime":1588259636,"maker_pubkey":"02987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","secret_hash":"9304bce3196f344b2a22dc99db406e95ab6f3107"}}},{"timestamp":1588244038271,"event":{"type":"TakerFeeSent","data":{"tx_hex":"0400008085202f8901bdde9bca02870787441f6068e4c2a869a3aac3d1d0925f6a6e27874343544d0a010000006a47304402206694a794693b55fbe8205cb1cbb992d92fa2a9a851763ad1bf1628c16deaf73e02203cfff465504bdd6c51e8fbd45dd2e1187142fdc29e82f36f577616d9d6097d7a012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff02dfceab07000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac39fd41892e0000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac46aeaa5e000000000000000000000000000000","tx_hash":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf","RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"total_amount":"2000","spent_by_me":"2000","received_by_me":"1998.71298873","my_balance_change":"-1.28701127","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6"}}}],"maker_amount":"999.99999","maker_coin":"MYCOIN","taker_amount":"999.99999","taker_coin":"MYCOIN1","gui":"nogui","mm_version":"UNKNOWN","success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"]}"#; +const FINISHED_TAKER_SWAP: &str = r#"{"type":"Taker","uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","events":[{"timestamp":1588244036253,"event":{"type":"Started","data":{"taker_coin":"MYCOIN1","maker_coin":"MYCOIN","maker":"987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","my_persistent_pub":"02859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521","lock_duration":7800,"maker_amount":"999.99999","taker_amount":"999.99999","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":1,"taker_payment_requires_nota":false,"taker_payment_lock":1588251836,"uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","started_at":1588244036,"maker_payment_wait":1588247156,"maker_coin_start_block":12,"taker_coin_start_block":11}}},{"timestamp":1588244038239,"event":{"type":"Negotiated","data":{"maker_payment_locktime":1588259636,"maker_pubkey":"02987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","secret_hash":"9304bce3196f344b2a22dc99db406e95ab6f3107"}}},{"timestamp":1588244038271,"event":{"type":"TakerFeeSent","data":{"tx_hex":"0400008085202f8901bdde9bca02870787441f6068e4c2a869a3aac3d1d0925f6a6e27874343544d0a010000006a47304402206694a794693b55fbe8205cb1cbb992d92fa2a9a851763ad1bf1628c16deaf73e02203cfff465504bdd6c51e8fbd45dd2e1187142fdc29e82f36f577616d9d6097d7a012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff02dfceab07000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac39fd41892e0000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac46aeaa5e000000000000000000000000000000","tx_hash":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf","RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"total_amount":"2000","spent_by_me":"2000","received_by_me":"1998.71298873","my_balance_change":"-1.28701127","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6"}}},{"timestamp":1588244038698,"event":{"type":"MakerPaymentReceived","data":{"tx_hex":"0400008085202f890163cc0aceb3b432f84c1407991a0389b74b7842f030aa261203aa0b4b9a9a15fd010000006b483045022100f3239794b7b0e1c75aae084a65535270f154231ce86a4739976ff69eeff1ebd402206c0aeb28b7b4b2e77e98b3c3efd9a20d85c2cd0627acc3f45d577c0e88c34fb4012102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dffffffff0118e476481700000017a914dc4ed4686174503b85374bfb0aefe07a9fb37bcf8746aeaa5e000000000000000000000000000000","tx_hash":"9a4c0b3f85ed0bb24dc9575ce7c2fd6bc50ad9d37c91c478946f9c33d15abfdf","from":["RAzSdYQhjCFdyhjrBz1AZQDVg3Hu8DrzYc"],"to":["bYp9ncp3V7FYsymipriVbdd3QL72hK9hio"],"total_amount":"1000","spent_by_me":"0","received_by_me":"0","my_balance_change":"0","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN","internal_id":"9a4c0b3f85ed0bb24dc9575ce7c2fd6bc50ad9d37c91c478946f9c33d15abfdf"}}},{"timestamp":1588244038699,"event":{"type":"MakerPaymentWaitConfirmStarted"}},{"timestamp":1588244053712,"event":{"type":"MakerPaymentValidatedAndConfirmed"}},{"timestamp":1588244053745,"event":{"type":"TakerPaymentSent","data":{"tx_hex":"0400008085202f8901c6272ed6f0900c449a6a6f948d74f85688228a843a672661ddbf1c4d48d71871010000006b483045022100c37627385c66b7bdf466b4dd4e7095b7551e6f5ea35f9bcc6344eb629f2edcb202203280eaba64b4d72010500166fab62cf34a687a516b2fe83d4eceaf8572cb37a7012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff0218e476481700000017a91431ff75ed72cd135a7ce50d121e71efc37066b9f9873915cb40170000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac55aeaa5e000000000000000000000000000000","tx_hash":"b4718ce94aa43f9073ab0b70c18ab4ea4b587338fb7110f20ff7d1bb452df08f","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC","bHHdqM8XWDHee2oyzydwUJKEE2BjofEtZH"],"total_amount":"1998.71298873","spent_by_me":"1998.71298873","received_by_me":"998.71298873","my_balance_change":"-1000","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"b4718ce94aa43f9073ab0b70c18ab4ea4b587338fb7110f20ff7d1bb452df08f"}}},{"timestamp":1588244078860,"event":{"type":"TakerPaymentSpent","data":{"transaction":{"tx_hex":"0400008085202f89018ff02d45bbd1f70ff21071fb3873584beab48ac1700bab73903fa44ae98c71b400000000d747304402204f0d641a3916e54d6788744c3229110a431eff18634c66fbd1741f9ca7dba99d02202315ee1d9317cc4d5d75d01f066f2c8a59876f790106d310144cdc03c25f985e0120dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498004c6b6304bcccaa5eb1752102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ac6782012088a9149304bce3196f344b2a22dc99db406e95ab6f3107882102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dac68ffffffff0130e07648170000001976a91412c553e8469363f2d30268c475af1e9186cc90af88ac54a0aa5e000000000000000000000000000000","tx_hash":"e7aed7a77e47b44dc9d12166589bbade70faea10b64888f73ed4be04bcc9f9a9","from":["bHHdqM8XWDHee2oyzydwUJKEE2BjofEtZH"],"to":["RAzSdYQhjCFdyhjrBz1AZQDVg3Hu8DrzYc"],"total_amount":"999.99999","spent_by_me":"0","received_by_me":"0","my_balance_change":"0","block_height":29,"timestamp":1588244070,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"e7aed7a77e47b44dc9d12166589bbade70faea10b64888f73ed4be04bcc9f9a9"},"secret":"dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498"}}},{"timestamp":1588244078870,"event":{"type":"MakerPaymentSpent","data":{"tx_hex":"0400008085202f8901dfbf5ad1339c6f9478c4917cd3d90ac56bfdc2e75c57c94db20bed853f0b4c9a00000000d848304502210092535c081325ba5261699d7cfd4c503fb6125dde86389b83f40f3e2c006039bb022063cfd72aa15558dee874cac08b22dbcf11d3f06c8e48b0ddaf75b86887d604410120dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498004c6b630434ebaa5eb1752102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dac6782012088a9149304bce3196f344b2a22dc99db406e95ab6f3107882102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ac68ffffffff0130e07648170000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac5ea0aa5e000000000000000000000000000000","tx_hash":"caea128b1c85a88abd5924e512780ee18952dadc217b0c06f4b2820eb71d03bc","from":["bYp9ncp3V7FYsymipriVbdd3QL72hK9hio"],"to":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"total_amount":"999.99999","spent_by_me":"0","received_by_me":"999.99998","my_balance_change":"999.99998","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN","internal_id":"caea128b1c85a88abd5924e512780ee18952dadc217b0c06f4b2820eb71d03bc"}}},{"timestamp":1588244078871,"event":{"type":"MakerPaymentSpendConfirmed"}},{"timestamp":1588244078872,"event":{"type":"Finished"}}],"maker_amount":"999.99999","maker_coin":"MYCOIN","taker_amount":"999.99999","taker_coin":"MYCOIN1","gui":"nogui","mm_version":"UNKNOWN","success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"]}"#; fn swap_file_lock_prevents_double_swap_start_on_kick_start(swap_json: &str) { let (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000.into()); diff --git a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs b/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs similarity index 64% rename from mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs rename to mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs index eade48b2c6..9fe3858736 100644 --- a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs @@ -1,23 +1,22 @@ use common::{block_on, log}; use mm2_number::BigDecimal; +use mm2_rpc::data::legacy::OrderbookResponse; use mm2_test_helpers::for_tests::{atom_testnet_conf, disable_coin, disable_coin_err, enable_tendermint, enable_tendermint_token, enable_tendermint_without_balance, - get_tendermint_my_tx_history, ibc_withdraw, iris_nimda_testnet_conf, - iris_testnet_conf, my_balance, send_raw_transaction, withdraw_v1, MarketMakerIt, - Mm2TestConf}; -use mm2_test_helpers::structs::{Bip44Chain, HDAccountAddressId, RpcV2Response, TendermintActivationResult, - TransactionDetails}; + get_tendermint_my_tx_history, ibc_withdraw, iris_ibc_nucleus_testnet_conf, + my_balance, nucleus_testnet_conf, orderbook, orderbook_v2, send_raw_transaction, + set_price, withdraw_v1, MarketMakerIt, Mm2TestConf}; +use mm2_test_helpers::structs::{Bip44Chain, HDAccountAddressId, OrderbookAddress, OrderbookV2Response, RpcV2Response, + TendermintActivationResult, TransactionDetails}; use serde_json::json; +use std::collections::HashSet; +use std::iter::FromIterator; -const ATOM_TEST_BALANCE_SEED: &str = "atom test seed"; -const ATOM_TEST_WITHDRAW_SEED: &str = "atom test withdraw seed"; -const ATOM_TICKER: &str = "ATOM"; -const ATOM_TENDERMINT_RPC_URLS: &[&str] = &["https://rpc.sentry-02.theta-testnet.polypore.xyz"]; +const TENDERMINT_TEST_SEED: &str = "tendermint test seed"; +const TENDERMINT_CONSTANT_BALANCE_SEED: &str = "tendermint constant balance seed"; -const IRIS_TEST_SEED: &str = "iris test seed"; -const IRIS_TESTNET_RPC_URLS: &[&str] = &["http://34.80.202.172:26657"]; - -const NUCLEUS_TESTNET_RPC_URLS: &[&str] = &["http://5.161.55.53:26657"]; +const ATOM_TENDERMINT_RPC_URLS: &[&str] = &["http://localhost:26658"]; +const NUCLEUS_TESTNET_RPC_URLS: &[&str] = &["http://localhost:26657"]; const TENDERMINT_TEST_BIP39_SEED: &str = "emerge canoe salmon dolphin glow priority random become gasp sell blade argue"; @@ -25,41 +24,36 @@ const TENDERMINT_TEST_BIP39_SEED: &str = #[test] fn test_tendermint_balance() { let coins = json!([atom_testnet_conf()]); - let expected_address = "cosmos1svaw0aqc4584x825ju7ua03g5xtxwd0ahl86hz"; + let coin = coins[0]["coin"].as_str().unwrap(); + let expected_address = "cosmos10tfc28dmn2m5qdrmg5ycjyqq7lyu7y8ledc8tc"; - let conf = Mm2TestConf::seednode(ATOM_TEST_BALANCE_SEED, &coins); + let conf = Mm2TestConf::seednode(TENDERMINT_CONSTANT_BALANCE_SEED, &coins); let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let activation_result = block_on(enable_tendermint( - &mm, - ATOM_TICKER, - &[], - ATOM_TENDERMINT_RPC_URLS, - false, - )); + let activation_result = block_on(enable_tendermint(&mm, coin, &[], ATOM_TENDERMINT_RPC_URLS, false)); let result: RpcV2Response = serde_json::from_value(activation_result).unwrap(); assert_eq!(result.result.address, expected_address); - let expected_balance: BigDecimal = "0.572203".parse().unwrap(); + let expected_balance: BigDecimal = "0.012345".parse().unwrap(); assert_eq!(result.result.balance.unwrap().spendable, expected_balance); - let my_balance = block_on(my_balance(&mm, ATOM_TICKER)); + let my_balance = block_on(my_balance(&mm, coin)); assert_eq!(my_balance.balance, expected_balance); assert_eq!(my_balance.unspendable_balance, BigDecimal::default()); assert_eq!(my_balance.address, expected_address); - assert_eq!(my_balance.coin, ATOM_TICKER); + assert_eq!(my_balance.coin, coin); } #[test] fn test_tendermint_activation_without_balance() { let coins = json!([atom_testnet_conf()]); - - let conf = Mm2TestConf::seednode(ATOM_TEST_BALANCE_SEED, &coins); + let coin = coins[0]["coin"].as_str().unwrap(); + let conf = Mm2TestConf::seednode(TENDERMINT_CONSTANT_BALANCE_SEED, &coins); let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); let activation_result = block_on(enable_tendermint_without_balance( &mm, - ATOM_TICKER, + coin, &[], ATOM_TENDERMINT_RPC_URLS, false, @@ -72,22 +66,92 @@ fn test_tendermint_activation_without_balance() { assert!(result.result.tokens_tickers.unwrap().is_empty()); } +#[test] +fn test_iris_ibc_nucleus_without_balance() { + let coins = json!([nucleus_testnet_conf(), iris_ibc_nucleus_testnet_conf()]); + + let conf = Mm2TestConf::seednode(TENDERMINT_TEST_SEED, &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let platform_coin = coins[0]["coin"].as_str().unwrap(); + let token = coins[1]["coin"].as_str().unwrap(); + + let activation_result = block_on(enable_tendermint_without_balance( + &mm, + platform_coin, + &[token], + NUCLEUS_TESTNET_RPC_URLS, + false, + )); + + let result: RpcV2Response = serde_json::from_value(activation_result).unwrap(); + + assert!(result.result.balance.is_none()); + assert!(result.result.tokens_balances.is_none()); + assert_eq!( + result.result.tokens_tickers.unwrap(), + HashSet::from_iter(vec![token.to_string()]) + ); +} + +#[test] +fn test_iris_ibc_nucleus_orderbook() { + let coins = json!([nucleus_testnet_conf(), iris_ibc_nucleus_testnet_conf()]); + let platform_coin = coins[0]["coin"].as_str().unwrap(); + let token = coins[1]["coin"].as_str().unwrap(); + + let conf = Mm2TestConf::seednode(TENDERMINT_TEST_SEED, &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let activation_result = block_on(enable_tendermint( + &mm, + platform_coin, + &[token], + NUCLEUS_TESTNET_RPC_URLS, + false, + )); + + let response: RpcV2Response = serde_json::from_value(activation_result).unwrap(); + + let expected_address = "nuc150evuj4j7k9kgu38e453jdv9m3u0ft2n4fgzfr"; + assert_eq!(response.result.address, expected_address); + + let set_price_res = block_on(set_price(&mm, token, platform_coin, "1", "0.1", false)); + log!("{:?}", set_price_res); + + let set_price_res = block_on(set_price(&mm, platform_coin, token, "1", "0.1", false)); + log!("{:?}", set_price_res); + + let orderbook = block_on(orderbook(&mm, token, platform_coin)); + let orderbook: OrderbookResponse = serde_json::from_value(orderbook).unwrap(); + + let first_ask = orderbook.asks.first().unwrap(); + assert_eq!(first_ask.entry.address, expected_address); + + let first_bid = orderbook.bids.first().unwrap(); + assert_eq!(first_bid.entry.address, expected_address); + + let orderbook_v2 = block_on(orderbook_v2(&mm, token, platform_coin)); + let orderbook_v2: RpcV2Response = serde_json::from_value(orderbook_v2).unwrap(); + + let expected_address = OrderbookAddress::Transparent(expected_address.into()); + let first_ask = orderbook_v2.result.asks.first().unwrap(); + assert_eq!(first_ask.entry.address, expected_address); + + let first_bid = orderbook_v2.result.bids.first().unwrap(); + assert_eq!(first_bid.entry.address, expected_address); +} + #[test] fn test_tendermint_hd_address() { let coins = json!([atom_testnet_conf()]); + let coin = coins[0]["coin"].as_str().unwrap(); // Default address m/44'/118'/0'/0/0 when no path_to_address is specified in activation request let expected_address = "cosmos1nv4mqaky7n7rqjhch7829kgypx5s8fh62wdtr8"; let conf = Mm2TestConf::seednode_with_hd_account(TENDERMINT_TEST_BIP39_SEED, &coins); let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let activation_result = block_on(enable_tendermint( - &mm, - ATOM_TICKER, - &[], - ATOM_TENDERMINT_RPC_URLS, - false, - )); + let activation_result = block_on(enable_tendermint(&mm, coin, &[], ATOM_TENDERMINT_RPC_URLS, false)); let result: RpcV2Response = serde_json::from_value(activation_result).unwrap(); assert_eq!(result.result.address, expected_address); @@ -95,26 +159,21 @@ fn test_tendermint_hd_address() { #[test] fn test_tendermint_withdraw() { - const MY_ADDRESS: &str = "cosmos1w5h6wud7a8zpa539rc99ehgl9gwkad3wjsjq8v"; + const MY_ADDRESS: &str = "cosmos150evuj4j7k9kgu38e453jdv9m3u0ft2n53flg6"; let coins = json!([atom_testnet_conf()]); + let coin = coins[0]["coin"].as_str().unwrap(); - let conf = Mm2TestConf::seednode(ATOM_TEST_WITHDRAW_SEED, &coins); + let conf = Mm2TestConf::seednode(TENDERMINT_TEST_SEED, &coins); let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let activation_res = block_on(enable_tendermint( - &mm, - ATOM_TICKER, - &[], - ATOM_TENDERMINT_RPC_URLS, - false, - )); + let activation_res = block_on(enable_tendermint(&mm, coin, &[], ATOM_TENDERMINT_RPC_URLS, false)); log!("Activation {}", serde_json::to_string(&activation_res).unwrap()); // just call withdraw without sending to check response correctness let tx_details = block_on(withdraw_v1( &mm, - ATOM_TICKER, + coin, "cosmos1svaw0aqc4584x825ju7ua03g5xtxwd0ahl86hz", "0.1", None, @@ -134,7 +193,7 @@ fn test_tendermint_withdraw() { assert_eq!(tx_details.from, vec![MY_ADDRESS.to_owned()]); // withdraw and send transaction to ourselves - let tx_details = block_on(withdraw_v1(&mm, ATOM_TICKER, MY_ADDRESS, "0.1", None)); + let tx_details = block_on(withdraw_v1(&mm, coin, MY_ADDRESS, "0.1", None)); log!("Withdraw to self {}", serde_json::to_string(&tx_details).unwrap()); // TODO how to check it if the fee is dynamic? @@ -151,21 +210,21 @@ fn test_tendermint_withdraw() { assert_eq!(tx_details.to, vec![MY_ADDRESS.to_owned()]); assert_eq!(tx_details.from, vec![MY_ADDRESS.to_owned()]); - let send_raw_tx = block_on(send_raw_transaction(&mm, ATOM_TICKER, &tx_details.tx_hex)); + let send_raw_tx = block_on(send_raw_transaction(&mm, coin, &tx_details.tx_hex)); log!("Send raw tx {}", serde_json::to_string(&send_raw_tx).unwrap()); } #[test] fn test_tendermint_withdraw_hd() { - const MY_ADDRESS: &str = "iaa1tpd0um0r3z0y88p3gkv3y38dq8lmqc2xs9u0pv"; + const MY_ADDRESS: &str = "cosmos134h9tv7866jcuw708w5w76lcfx7s3x2ysyalxy"; - let coins = json!([iris_testnet_conf()]); + let coins = json!([atom_testnet_conf()]); let coin = coins[0]["coin"].as_str().unwrap(); let conf = Mm2TestConf::seednode_with_hd_account(TENDERMINT_TEST_BIP39_SEED, &coins); let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let activation_res = block_on(enable_tendermint(&mm, coin, &[], IRIS_TESTNET_RPC_URLS, false)); + let activation_res = block_on(enable_tendermint(&mm, coin, &[], ATOM_TENDERMINT_RPC_URLS, false)); log!( "Activation with assets {}", serde_json::to_string(&activation_res).unwrap() @@ -182,7 +241,7 @@ fn test_tendermint_withdraw_hd() { let tx_details = block_on(withdraw_v1( &mm, coin, - "iaa1llp0f6qxemgh4g4m5ewk0ew0hxj76avuz8kwd5", + "cosmos1g3ufk7awmktp6kr2kgzfvlhm4ujzq3ekk9j3n3", "0.1", Some(path_to_address.clone()), )); @@ -196,7 +255,7 @@ fn test_tendermint_withdraw_hd() { */ assert_eq!(tx_details.received_by_me, BigDecimal::default()); assert_eq!(tx_details.to, vec![ - "iaa1llp0f6qxemgh4g4m5ewk0ew0hxj76avuz8kwd5".to_owned() + "cosmos1g3ufk7awmktp6kr2kgzfvlhm4ujzq3ekk9j3n3".to_owned() ]); assert_eq!(tx_details.from, vec![MY_ADDRESS.to_owned()]); @@ -225,23 +284,18 @@ fn test_tendermint_withdraw_hd() { #[test] fn test_custom_gas_limit_on_tendermint_withdraw() { let coins = json!([atom_testnet_conf()]); + let coin = coins[0]["coin"].as_str().unwrap(); - let conf = Mm2TestConf::seednode(ATOM_TEST_WITHDRAW_SEED, &coins); + let conf = Mm2TestConf::seednode(TENDERMINT_TEST_SEED, &coins); let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let activation_res = block_on(enable_tendermint( - &mm, - ATOM_TICKER, - &[], - ATOM_TENDERMINT_RPC_URLS, - false, - )); + let activation_res = block_on(enable_tendermint(&mm, coin, &[], ATOM_TENDERMINT_RPC_URLS, false)); log!("Activation {}", serde_json::to_string(&activation_res).unwrap()); let request = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "withdraw", - "coin": ATOM_TICKER, + "coin": coin, "to": "cosmos1w5h6wud7a8zpa539rc99ehgl9gwkad3wjsjq8v", "amount": "0.1", "fee": { @@ -257,36 +311,36 @@ fn test_custom_gas_limit_on_tendermint_withdraw() { assert_eq!(tx_details.fee_details["gas_limit"], 150000); } -// Ignored because IBC clients aren't maintained and get expired. #[test] -#[ignore] -fn test_tendermint_token_ibc_withdraw() { - // visit `{rpc_url}/ibc/core/channel/v1/channels?pagination.limit=10000` to see the full list of ibc channels - const IBC_SOURCE_CHANNEL: &str = "channel-151"; +fn test_tendermint_ibc_withdraw() { + // visit `{swagger_address}/ibc/core/channel/v1/channels?pagination.limit=10000` to see the full list of ibc channels + const IBC_SOURCE_CHANNEL: &str = "channel-3"; const IBC_TARGET_ADDRESS: &str = "cosmos1r5v5srda7xfth3hn2s26txvrcrntldjumt8mhl"; - const MY_ADDRESS: &str = "iaa1e0rx87mdj79zejewuc4jg7ql9ud2286g2us8f2"; + const MY_ADDRESS: &str = "nuc150evuj4j7k9kgu38e453jdv9m3u0ft2n4fgzfr"; - let coins = json!([iris_testnet_conf(), iris_nimda_testnet_conf()]); + let coins = json!([nucleus_testnet_conf()]); let platform_coin = coins[0]["coin"].as_str().unwrap(); - let token = coins[1]["coin"].as_str().unwrap(); - let conf = Mm2TestConf::seednode(IRIS_TEST_SEED, &coins); + let conf = Mm2TestConf::seednode(TENDERMINT_TEST_SEED, &coins); let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let activation_res = block_on(enable_tendermint(&mm, platform_coin, &[], IRIS_TESTNET_RPC_URLS, false)); + let activation_res = block_on(enable_tendermint( + &mm, + platform_coin, + &[], + NUCLEUS_TESTNET_RPC_URLS, + false, + )); log!( "Activation with assets {}", serde_json::to_string(&activation_res).unwrap() ); - let activation_res = block_on(enable_tendermint_token(&mm, token)); - log!("Token activation {}", serde_json::to_string(&activation_res).unwrap()); - let tx_details = block_on(ibc_withdraw( &mm, IBC_SOURCE_CHANNEL, - token, + platform_coin, IBC_TARGET_ADDRESS, "0.1", None, @@ -296,33 +350,28 @@ fn test_tendermint_token_ibc_withdraw() { serde_json::to_string(&tx_details).unwrap() ); - let expected_spent: BigDecimal = "0.1".parse().unwrap(); - assert_eq!(tx_details.spent_by_me, expected_spent); - assert_eq!(tx_details.to, vec![IBC_TARGET_ADDRESS.to_owned()]); assert_eq!(tx_details.from, vec![MY_ADDRESS.to_owned()]); - let send_raw_tx = block_on(send_raw_transaction(&mm, token, &tx_details.tx_hex)); + let send_raw_tx = block_on(send_raw_transaction(&mm, platform_coin, &tx_details.tx_hex)); log!("Send raw tx {}", serde_json::to_string(&send_raw_tx).unwrap()); } -// Ignored because IBC clients aren't maintained and get expired. #[test] -#[ignore] fn test_tendermint_ibc_withdraw_hd() { - // visit `{rpc_url}/ibc/core/channel/v1/channels?pagination.limit=10000` to see the full list of ibc channels - const IBC_SOURCE_CHANNEL: &str = "channel-152"; + // visit `{swagger_address}/ibc/core/channel/v1/channels?pagination.limit=10000` to see the full list of ibc channels + const IBC_SOURCE_CHANNEL: &str = "channel-3"; - const IBC_TARGET_ADDRESS: &str = "cosmos1r5v5srda7xfth3hn2s26txvrcrntldjumt8mhl"; - const MY_ADDRESS: &str = "iaa1tpd0um0r3z0y88p3gkv3y38dq8lmqc2xs9u0pv"; + const IBC_TARGET_ADDRESS: &str = "nuc150evuj4j7k9kgu38e453jdv9m3u0ft2n4fgzfr"; + const MY_ADDRESS: &str = "cosmos134h9tv7866jcuw708w5w76lcfx7s3x2ysyalxy"; - let coins = json!([iris_testnet_conf()]); + let coins = json!([atom_testnet_conf()]); let coin = coins[0]["coin"].as_str().unwrap(); let conf = Mm2TestConf::seednode_with_hd_account(TENDERMINT_TEST_BIP39_SEED, &coins); let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let activation_res = block_on(enable_tendermint(&mm, coin, &[], IRIS_TESTNET_RPC_URLS, false)); + let activation_res = block_on(enable_tendermint(&mm, coin, &[], ATOM_TENDERMINT_RPC_URLS, false)); log!( "Activation with assets {}", serde_json::to_string(&activation_res).unwrap() @@ -340,11 +389,11 @@ fn test_tendermint_ibc_withdraw_hd() { IBC_SOURCE_CHANNEL, coin, IBC_TARGET_ADDRESS, - "0.1", + "0.061", Some(path_to_address), )); log!( - "IBC transfer to atom address {}", + "IBC transfer to nucleus address {}", serde_json::to_string(&tx_details).unwrap() ); @@ -357,16 +406,22 @@ fn test_tendermint_ibc_withdraw_hd() { #[test] fn test_tendermint_token_withdraw() { - const MY_ADDRESS: &str = "iaa1e0rx87mdj79zejewuc4jg7ql9ud2286g2us8f2"; + const MY_ADDRESS: &str = "nuc150evuj4j7k9kgu38e453jdv9m3u0ft2n4fgzfr"; - let coins = json!([iris_testnet_conf(), iris_nimda_testnet_conf()]); + let coins = json!([nucleus_testnet_conf(), iris_ibc_nucleus_testnet_conf()]); let platform_coin = coins[0]["coin"].as_str().unwrap(); let token = coins[1]["coin"].as_str().unwrap(); - let conf = Mm2TestConf::seednode(IRIS_TEST_SEED, &coins); + let conf = Mm2TestConf::seednode(TENDERMINT_TEST_SEED, &coins); let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let activation_res = block_on(enable_tendermint(&mm, platform_coin, &[], IRIS_TESTNET_RPC_URLS, false)); + let activation_res = block_on(enable_tendermint( + &mm, + platform_coin, + &[], + NUCLEUS_TESTNET_RPC_URLS, + false, + )); log!( "Activation with assets {}", serde_json::to_string(&activation_res).unwrap() @@ -379,7 +434,7 @@ fn test_tendermint_token_withdraw() { let tx_details = block_on(withdraw_v1( &mm, token, - "iaa1llp0f6qxemgh4g4m5ewk0ew0hxj76avuz8kwd5", + "nuc1k2zmvy4kyxdfxv085kjlrygz2d78g78ew365gq", "0.1", None, )); @@ -400,7 +455,7 @@ fn test_tendermint_token_withdraw() { assert_eq!(tx_details.my_balance_change, expected_total * BigDecimal::from(-1)); assert_eq!(tx_details.received_by_me, BigDecimal::default()); assert_eq!(tx_details.to, vec![ - "iaa1llp0f6qxemgh4g4m5ewk0ew0hxj76avuz8kwd5".to_owned() + "nuc1k2zmvy4kyxdfxv085kjlrygz2d78g78ew365gq".to_owned() ]); assert_eq!(tx_details.from, vec![MY_ADDRESS.to_owned()]); @@ -432,20 +487,21 @@ fn test_tendermint_token_withdraw() { #[test] fn test_tendermint_tx_history() { const TEST_SEED: &str = "Vdo8Xt8pTAetRlMq3kV0LzE393eVYbPSn5Mhtw4p"; - const TX_FINISHED_LOG: &str = "Tx history fetching finished for IRIS-TEST."; + const TX_FINISHED_LOG: &str = "Tx history fetching finished for NUCLEUS-TEST."; const TX_HISTORY_PAGE_LIMIT: usize = 50; - const IRIS_TEST_EXPECTED_TX_COUNT: u64 = 16; - const IRIS_NIMDA_EXPECTED_TX_COUNT: u64 = 10; + const NUCLEUS_EXPECTED_TX_COUNT: u64 = 9; + const IRIS_IBC_EXPECTED_TX_COUNT: u64 = 1; - let iris_test_constant_history_txs = include_str!("../../../mm2_test_helpers/dummy_files/iris_test_history.json"); - let iris_test_constant_history_txs: Vec = - serde_json::from_str(iris_test_constant_history_txs).unwrap(); + let nucleus_constant_history_txs = include_str!("../../../mm2_test_helpers/dummy_files/nucleus-history.json"); + let nucleus_constant_history_txs: Vec = + serde_json::from_str(nucleus_constant_history_txs).unwrap(); - let iris_nimda_constant_history_txs = include_str!("../../../mm2_test_helpers/dummy_files/iris_nimda_history.json"); - let iris_nimda_constant_history_txs: Vec = - serde_json::from_str(iris_nimda_constant_history_txs).unwrap(); + let iris_ibc_constant_history_txs = + include_str!("../../../mm2_test_helpers/dummy_files/iris-ibc-nucleus-history.json"); + let iris_ibc_constant_history_txs: Vec = + serde_json::from_str(iris_ibc_constant_history_txs).unwrap(); - let coins = json!([iris_testnet_conf(), iris_nimda_testnet_conf()]); + let coins = json!([nucleus_testnet_conf(), iris_ibc_nucleus_testnet_conf()]); let platform_coin = coins[0]["coin"].as_str().unwrap(); let token = coins[1]["coin"].as_str().unwrap(); @@ -456,7 +512,7 @@ fn test_tendermint_tx_history() { &mm, platform_coin, &[token], - IRIS_TESTNET_RPC_URLS, + NUCLEUS_TESTNET_RPC_URLS, true, )); @@ -465,220 +521,157 @@ fn test_tendermint_tx_history() { panic!("Tx history didn't finish which is not expected"); } - // testing IRIS-TEST history - let iris_tx_history_response = block_on(get_tendermint_my_tx_history( + // testing NUCLEUS-TEST history + let nucleus_history_response = block_on(get_tendermint_my_tx_history( &mm, platform_coin, TX_HISTORY_PAGE_LIMIT, 1, )); - let total_txs = iris_tx_history_response["result"]["total"].as_u64().unwrap(); - assert_eq!(total_txs, IRIS_TEST_EXPECTED_TX_COUNT); + let total_txs = nucleus_history_response["result"]["total"].as_u64().unwrap(); + assert_eq!(total_txs, NUCLEUS_EXPECTED_TX_COUNT); - let mut iris_txs_from_request = iris_tx_history_response["result"]["transactions"].clone(); - for i in 0..IRIS_TEST_EXPECTED_TX_COUNT { - iris_txs_from_request[i as usize] + let mut nucleus_txs_from_request = nucleus_history_response["result"]["transactions"].clone(); + for i in 0..NUCLEUS_EXPECTED_TX_COUNT { + nucleus_txs_from_request[i as usize] .as_object_mut() .unwrap() .remove("confirmations"); } - let iris_txs_from_request: Vec = serde_json::from_value(iris_txs_from_request).unwrap(); - assert_eq!(iris_test_constant_history_txs, iris_txs_from_request); + let nucleus_txs_from_request: Vec = serde_json::from_value(nucleus_txs_from_request).unwrap(); + assert_eq!(nucleus_constant_history_txs, nucleus_txs_from_request); - // testing IRIS-NIMDA history - let nimda_tx_history_response = block_on(get_tendermint_my_tx_history(&mm, token, TX_HISTORY_PAGE_LIMIT, 1)); - let total_txs = nimda_tx_history_response["result"]["total"].as_u64().unwrap(); - assert_eq!(total_txs, IRIS_NIMDA_EXPECTED_TX_COUNT); + // testing IRIS-IBC-NUCLEUS-TEST history + let iris_ibc_tx_history_response = block_on(get_tendermint_my_tx_history(&mm, token, TX_HISTORY_PAGE_LIMIT, 1)); + let total_txs = iris_ibc_tx_history_response["result"]["total"].as_u64().unwrap(); + assert_eq!(total_txs, IRIS_IBC_EXPECTED_TX_COUNT); - let mut nimda_txs_from_request = nimda_tx_history_response["result"]["transactions"].clone(); - for i in 0..IRIS_NIMDA_EXPECTED_TX_COUNT { - nimda_txs_from_request[i as usize] + let mut iris_ibc_txs_from_request = iris_ibc_tx_history_response["result"]["transactions"].clone(); + for i in 0..IRIS_IBC_EXPECTED_TX_COUNT { + iris_ibc_txs_from_request[i as usize] .as_object_mut() .unwrap() .remove("confirmations"); } - let nimda_txs_from_request: Vec = serde_json::from_value(nimda_txs_from_request).unwrap(); + let iris_ibc_txs_from_request: Vec = serde_json::from_value(iris_ibc_txs_from_request).unwrap(); - assert_eq!(iris_nimda_constant_history_txs, nimda_txs_from_request); + assert_eq!(iris_ibc_constant_history_txs, iris_ibc_txs_from_request); block_on(mm.stop()).unwrap(); } #[test] fn test_disable_tendermint_platform_coin_with_token() { - const TEST_SEED: &str = "iris test seed"; - let coins = json!([iris_testnet_conf(), iris_nimda_testnet_conf()]); + let coins = json!([nucleus_testnet_conf(), iris_ibc_nucleus_testnet_conf()]); let platform_coin = coins[0]["coin"].as_str().unwrap(); let token = coins[1]["coin"].as_str().unwrap(); - let conf = Mm2TestConf::seednode(TEST_SEED, &coins); + let conf = Mm2TestConf::seednode(TENDERMINT_TEST_SEED, &coins); let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - // Enable platform coin IRIS-TEST - let activation_res = block_on(enable_tendermint(&mm, platform_coin, &[], IRIS_TESTNET_RPC_URLS, false)); + // Enable platform coin NUCLEUS-TEST + let activation_res = block_on(enable_tendermint( + &mm, + platform_coin, + &[], + NUCLEUS_TESTNET_RPC_URLS, + false, + )); assert!(&activation_res.get("result").unwrap().get("address").is_some()); - // Enable platform coin token IRIS-NIMDA + // Enable platform coin token IRIS-IBC-NUCLEUS-TEST let activation_res = block_on(enable_tendermint_token(&mm, token)); assert!(&activation_res.get("result").unwrap().get("balances").is_some()); - // Try to passive platform coin, IRIS-TEST. - let res = block_on(disable_coin(&mm, "IRIS-TEST", false)); + // Try to passive platform coin + let res = block_on(disable_coin(&mm, platform_coin, false)); assert!(res.passivized); - // Try to disable IRIS-NIMDA token when platform coin is passived. + // Try to disable token when platform coin is passived. // This should work, because platform coin is still in the memory. - let res = block_on(disable_coin(&mm, "IRIS-NIMDA", false)); + let res = block_on(disable_coin(&mm, token, false)); assert!(!res.passivized); - // Then try to force disable IRIS-TEST platform coin. - let res = block_on(disable_coin(&mm, "IRIS-TEST", true)); + // Then try to force disable platform coin. + let res = block_on(disable_coin(&mm, platform_coin, true)); assert!(!res.passivized); } #[test] fn test_passive_coin_and_force_disable() { - const TEST_SEED: &str = "iris test seed"; - let coins = json!([iris_testnet_conf(), iris_nimda_testnet_conf()]); + let coins = json!([nucleus_testnet_conf(), iris_ibc_nucleus_testnet_conf()]); let platform_coin = coins[0]["coin"].as_str().unwrap(); let token = coins[1]["coin"].as_str().unwrap(); - let conf = Mm2TestConf::seednode(TEST_SEED, &coins); + let conf = Mm2TestConf::seednode(TENDERMINT_TEST_SEED, &coins); let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - // Enable platform coin IRIS-TEST - let activation_res = block_on(enable_tendermint(&mm, platform_coin, &[], IRIS_TESTNET_RPC_URLS, false)); + // Enable platform coin NUCLEUS-TEST + let activation_res = block_on(enable_tendermint( + &mm, + platform_coin, + &[], + NUCLEUS_TESTNET_RPC_URLS, + false, + )); assert!(&activation_res.get("result").unwrap().get("address").is_some()); - // Enable platform coin token IRIS-NIMDA + // Enable platform coin token IRIS-IBC-NUCLEUS-TEST let activation_res = block_on(enable_tendermint_token(&mm, token)); assert!(&activation_res.get("result").unwrap().get("balances").is_some()); - // Try to passive platform coin, IRIS-TEST. - let res = block_on(disable_coin(&mm, "IRIS-TEST", false)); + // Try to passive platform coin + let res = block_on(disable_coin(&mm, platform_coin, false)); assert!(res.passivized); - // Try to disable IRIS-NIMDA token when platform coin is passived. + // Try to disable token when platform coin is passived. // This should work, because platform coin is still in the memory. - let res = block_on(disable_coin(&mm, "IRIS-NIMDA", false)); + let res = block_on(disable_coin(&mm, token, false)); assert!(!res.passivized); // Re-activate passive coin - let activation_res = block_on(enable_tendermint(&mm, platform_coin, &[], IRIS_TESTNET_RPC_URLS, false)); + let activation_res = block_on(enable_tendermint( + &mm, + platform_coin, + &[], + NUCLEUS_TESTNET_RPC_URLS, + false, + )); assert!(&activation_res.get("result").unwrap().get("address").is_some()); - // Enable platform coin token IRIS-NIMDA + // Enable platform coin token let activation_res = block_on(enable_tendermint_token(&mm, token)); assert!(&activation_res.get("result").unwrap().get("balances").is_some()); - // Try to force disable platform coin, IRIS-TEST. - let res = block_on(disable_coin(&mm, "IRIS-TEST", true)); + // Try to force disable platform coin + let res = block_on(disable_coin(&mm, platform_coin, true)); assert!(!res.passivized); - // Try to disable IRIS-NIMDA token when platform coin force disabled. - // This should failed, because platform coin was purged with it's tokens. - block_on(disable_coin_err(&mm, "IRIS-NIMDA", false)); + // Try to disable token when platform coin force disabled. + // This should failed, because platform coin was purged with its tokens. + block_on(disable_coin_err(&mm, token, false)); } mod swap { use super::*; + use crate::docker_tests::eth_docker_tests::fill_eth; + use crate::docker_tests::eth_docker_tests::swap_contract; use crate::integration_tests_common::enable_electrum; use common::executor::Timer; use common::log; + use ethereum_types::{Address, U256}; use instant::Duration; use mm2_rpc::data::legacy::OrderbookResponse; use mm2_test_helpers::for_tests::{check_my_swap_status, check_recent_swaps, doc_conf, enable_eth_coin, - iris_ibc_nucleus_testnet_conf, nucleus_testnet_conf, tbnb_conf, - usdc_ibc_iris_testnet_conf, wait_check_stats_swap_status, DOC_ELECTRUM_ADDRS}; + iris_ibc_nucleus_testnet_conf, nucleus_testnet_conf, + wait_check_stats_swap_status, DOC_ELECTRUM_ADDRS}; use std::convert::TryFrom; + use std::str::FromStr; use std::{env, thread}; const BOB_PASSPHRASE: &str = "iris test seed"; const ALICE_PASSPHRASE: &str = "iris test2 seed"; - // https://academy.binance.com/en/articles/connecting-metamask-to-binance-smart-chain - const TBNB_URLS: &[&str] = &["https://data-seed-prebsc-1-s1.binance.org:8545/"]; - // https://testnet.bscscan.com/address/0xb1ad803ea4f57401639c123000c75f5b66e4d123 - const TBNB_SWAP_CONTRACT: &str = "0xB1Ad803ea4F57401639c123000C75F5B66E4D123"; - - #[test] - fn swap_usdc_ibc_with_nimda() { - let bob_passphrase = String::from(BOB_PASSPHRASE); - let alice_passphrase = String::from(ALICE_PASSPHRASE); - - let coins = json!([ - usdc_ibc_iris_testnet_conf(), - iris_nimda_testnet_conf(), - iris_testnet_conf(), - ]); - - let mm_bob = MarketMakerIt::start( - json!({ - "gui": "nogui", - "netid": 8999, - "dht": "on", - "myipaddr": env::var("BOB_TRADE_IP") .ok(), - "rpcip": env::var("BOB_TRADE_IP") .ok(), - "canbind": env::var("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - None, - ) - .unwrap(); - - thread::sleep(Duration::from_secs(1)); - - let mm_alice = MarketMakerIt::start( - json!({ - "gui": "nogui", - "netid": 8999, - "dht": "on", - "myipaddr": env::var("ALICE_TRADE_IP") .ok(), - "rpcip": env::var("ALICE_TRADE_IP") .ok(), - "passphrase": alice_passphrase, - "coins": coins, - "seednodes": [mm_bob.my_seed_addr()], - "rpc_password": "password", - "skip_startup_checks": true, - }), - "password".into(), - None, - ) - .unwrap(); - - thread::sleep(Duration::from_secs(1)); - - dbg!(block_on(enable_tendermint( - &mm_bob, - "IRIS-TEST", - &["IRIS-NIMDA", "USDC-IBC-IRIS"], - IRIS_TESTNET_RPC_URLS, - false - ))); - - dbg!(block_on(enable_tendermint( - &mm_alice, - "IRIS-TEST", - &["IRIS-NIMDA", "USDC-IBC-IRIS"], - IRIS_TESTNET_RPC_URLS, - false - ))); - - block_on(trade_base_rel_tendermint( - mm_bob, - mm_alice, - "USDC-IBC-IRIS", - "IRIS-NIMDA", - 1, - 2, - 0.008, - )); - } - #[test] fn swap_nucleus_with_doc() { let bob_passphrase = String::from(BOB_PASSPHRASE); @@ -758,11 +751,23 @@ mod swap { } #[test] - fn swap_doc_with_nucleus() { + fn swap_nucleus_with_eth() { let bob_passphrase = String::from(BOB_PASSPHRASE); let alice_passphrase = String::from(ALICE_PASSPHRASE); + const BOB_ETH_ADDRESS: &str = "0x7b338250f990954E3Ab034ccD32a917c2F607C2d"; + const ALICE_ETH_ADDRESS: &str = "0x37602b7a648b207ACFD19E67253f57669bEA4Ad8"; - let coins = json!([nucleus_testnet_conf(), doc_conf()]); + fill_eth( + Address::from_str(BOB_ETH_ADDRESS).unwrap(), + U256::from(10).pow(U256::from(20)), + ); + + fill_eth( + Address::from_str(ALICE_ETH_ADDRESS).unwrap(), + U256::from(10).pow(U256::from(20)), + ); + + let coins = json!([nucleus_testnet_conf(), crate::eth_dev_conf()]); let mm_bob = MarketMakerIt::start( json!({ @@ -820,93 +825,31 @@ mod swap { false ))); - dbg!(block_on(enable_electrum(&mm_bob, "DOC", false, DOC_ELECTRUM_ADDRS))); - - dbg!(block_on(enable_electrum(&mm_alice, "DOC", false, DOC_ELECTRUM_ADDRS))); - - block_on(trade_base_rel_tendermint( - mm_bob, - mm_alice, - "DOC", - "NUCLEUS-TEST", - 1, - 2, - 0.008, - )); - } - - #[test] - fn swap_iris_ibc_nucleus_with_doc() { - let bob_passphrase = String::from(BOB_PASSPHRASE); - let alice_passphrase = String::from(ALICE_PASSPHRASE); - - let coins = json!([nucleus_testnet_conf(), iris_ibc_nucleus_testnet_conf(), doc_conf()]); - - let mm_bob = MarketMakerIt::start( - json!({ - "gui": "nogui", - "netid": 8999, - "dht": "on", - "myipaddr": env::var("BOB_TRADE_IP") .ok(), - "rpcip": env::var("BOB_TRADE_IP") .ok(), - "canbind": env::var("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - None, - ) - .unwrap(); - - thread::sleep(Duration::from_secs(1)); - - let mm_alice = MarketMakerIt::start( - json!({ - "gui": "nogui", - "netid": 8999, - "dht": "on", - "myipaddr": env::var("ALICE_TRADE_IP") .ok(), - "rpcip": env::var("ALICE_TRADE_IP") .ok(), - "passphrase": alice_passphrase, - "coins": coins, - "seednodes": [mm_bob.my_seed_addr()], - "rpc_password": "password", - "skip_startup_checks": true, - }), - "password".into(), - None, - ) - .unwrap(); - - thread::sleep(Duration::from_secs(1)); + let swap_contract = format!("0x{}", hex::encode(swap_contract())); - dbg!(block_on(enable_tendermint( + dbg!(block_on(enable_eth_coin( &mm_bob, - "NUCLEUS-TEST", - &["IRIS-IBC-NUCLEUS-TEST"], - NUCLEUS_TESTNET_RPC_URLS, + "ETH", + &[crate::GETH_RPC_URL], + &swap_contract, + None, false ))); - dbg!(block_on(enable_tendermint( + dbg!(block_on(enable_eth_coin( &mm_alice, - "NUCLEUS-TEST", - &["IRIS-IBC-NUCLEUS-TEST"], - NUCLEUS_TESTNET_RPC_URLS, + "ETH", + &[crate::GETH_RPC_URL], + &swap_contract, + None, false ))); - dbg!(block_on(enable_electrum(&mm_bob, "DOC", false, DOC_ELECTRUM_ADDRS))); - - dbg!(block_on(enable_electrum(&mm_alice, "DOC", false, DOC_ELECTRUM_ADDRS))); - block_on(trade_base_rel_tendermint( mm_bob, mm_alice, - "IRIS-IBC-NUCLEUS-TEST", - "DOC", + "NUCLEUS-TEST", + "ETH", 1, 2, 0.008, @@ -991,99 +934,6 @@ mod swap { )); } - #[test] - #[ignore] // having fund problems with tBNB - fn swap_iris_with_tbnb() { - let bob_passphrase = String::from(BOB_PASSPHRASE); - let alice_passphrase = String::from(ALICE_PASSPHRASE); - - let coins = json!([iris_testnet_conf(), tbnb_conf()]); - - let mm_bob = MarketMakerIt::start( - json!({ - "gui": "nogui", - "netid": 8999, - "dht": "on", - "myipaddr": env::var("BOB_TRADE_IP") .ok(), - "rpcip": env::var("BOB_TRADE_IP") .ok(), - "canbind": env::var("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - None, - ) - .unwrap(); - - thread::sleep(Duration::from_secs(1)); - - let mm_alice = MarketMakerIt::start( - json!({ - "gui": "nogui", - "netid": 8999, - "dht": "on", - "myipaddr": env::var("ALICE_TRADE_IP") .ok(), - "rpcip": env::var("ALICE_TRADE_IP") .ok(), - "passphrase": alice_passphrase, - "coins": coins, - "seednodes": [mm_bob.my_seed_addr()], - "rpc_password": "password", - "skip_startup_checks": true, - }), - "password".into(), - None, - ) - .unwrap(); - - thread::sleep(Duration::from_secs(1)); - - dbg!(block_on(enable_tendermint( - &mm_bob, - "IRIS-TEST", - &[], - IRIS_TESTNET_RPC_URLS, - false - ))); - - dbg!(block_on(enable_tendermint( - &mm_alice, - "IRIS-TEST", - &[], - IRIS_TESTNET_RPC_URLS, - false - ))); - - dbg!(block_on(enable_eth_coin( - &mm_bob, - "tBNB", - TBNB_URLS, - TBNB_SWAP_CONTRACT, - None, - false - ))); - - dbg!(block_on(enable_eth_coin( - &mm_alice, - "tBNB", - TBNB_URLS, - TBNB_SWAP_CONTRACT, - None, - false - ))); - - block_on(trade_base_rel_tendermint( - mm_bob, - mm_alice, - "IRIS-TEST", - "tBNB", - 1, - 2, - 0.008, - )); - } - pub async fn trade_base_rel_tendermint( mut mm_bob: MarketMakerIt, mut mm_alice: MarketMakerIt, diff --git a/mm2src/mm2_main/tests/docker_tests_main.rs b/mm2src/mm2_main/tests/docker_tests_main.rs index e2147e3cea..707f558631 100644 --- a/mm2src/mm2_main/tests/docker_tests_main.rs +++ b/mm2src/mm2_main/tests/docker_tests_main.rs @@ -22,8 +22,12 @@ extern crate serde_json; #[cfg(test)] extern crate ser_error_derive; #[cfg(test)] extern crate test; +use common::custom_futures::timeout::FutureTimerExt; +use std::env; use std::io::{BufRead, BufReader}; +use std::path::PathBuf; use std::process::Command; +use std::time::Duration; use test::{test_main, StaticBenchFn, StaticTestFn, TestDescAndFn}; use testcontainers::clients::Cli; @@ -46,14 +50,26 @@ pub fn docker_tests_runner(tests: &[&TestDescAndFn]) { let docker = Cli::default(); let mut containers = vec![]; // skip Docker containers initialization if we are intended to run test_mm_start only - if std::env::var("_MM2_TEST_CONF").is_err() { - pull_docker_image(UTXO_ASSET_DOCKER_IMAGE_WITH_TAG); - pull_docker_image(QTUM_REGTEST_DOCKER_IMAGE_WITH_TAG); - pull_docker_image(GETH_DOCKER_IMAGE_WITH_TAG); - remove_docker_containers(UTXO_ASSET_DOCKER_IMAGE_WITH_TAG); - remove_docker_containers(QTUM_REGTEST_DOCKER_IMAGE_WITH_TAG); - remove_docker_containers(GETH_DOCKER_IMAGE_WITH_TAG); + if env::var("_MM2_TEST_CONF").is_err() { + const IMAGES: &[&str] = &[ + UTXO_ASSET_DOCKER_IMAGE_WITH_TAG, + QTUM_REGTEST_DOCKER_IMAGE_WITH_TAG, + GETH_DOCKER_IMAGE_WITH_TAG, + NUCLEUS_IMAGE, + ATOM_IMAGE_WITH_TAG, + IBC_RELAYER_IMAGE_WITH_TAG, + ]; + for image in IMAGES { + pull_docker_image(image); + remove_docker_containers(image); + } + + let runtime_dir = prepare_runtime_dir().unwrap(); + + let nucleus_node = nucleus_node(&docker, runtime_dir.clone()); + let atom_node = atom_node(&docker, runtime_dir.clone()); + let ibc_relayer_node = ibc_relayer_node(&docker, runtime_dir); let utxo_node = utxo_asset_docker_node(&docker, "MYCOIN", 7000); let utxo_node1 = utxo_asset_docker_node(&docker, "MYCOIN1", 8000); let qtum_node = qtum_docker_node(&docker, 9000); @@ -72,13 +88,21 @@ pub fn docker_tests_runner(tests: &[&TestDescAndFn]) { utxo_ops.wait_ready(4); utxo_ops1.wait_ready(4); + wait_for_geth_node_ready(); init_geth_node(); + prepare_ibc_channels(ibc_relayer_node.container.id()); + + thread::sleep(Duration::from_secs(10)); + wait_until_relayer_container_is_ready(ibc_relayer_node.container.id()); containers.push(utxo_node); containers.push(utxo_node1); containers.push(qtum_node); containers.push(for_slp_node); containers.push(geth_node); + containers.push(nucleus_node); + containers.push(atom_node); + containers.push(ibc_relayer_node); } // detect if docker is installed // skip the tests that use docker if not installed @@ -96,10 +120,33 @@ pub fn docker_tests_runner(tests: &[&TestDescAndFn]) { _ => panic!("non-static tests passed to lp_coins test runner"), }) .collect(); - let args: Vec = std::env::args().collect(); + let args: Vec = env::args().collect(); test_main(&args, owned_tests, None); } +fn wait_for_geth_node_ready() { + let mut attempts = 0; + loop { + if attempts >= 5 { + panic!("Failed to connect to Geth node after several attempts."); + } + match block_on(GETH_WEB3.eth().block_number().timeout(Duration::from_secs(6))) { + Ok(Ok(block_number)) => { + log!("Geth node is ready, latest block number: {:?}", block_number); + break; + }, + Ok(Err(e)) => { + log!("Failed to connect to Geth node: {:?}, retrying...", e); + }, + Err(_) => { + log!("Connection to Geth node timed out, retrying..."); + }, + } + attempts += 1; + thread::sleep(Duration::from_secs(1)); + } +} + fn pull_docker_image(name: &str) { Command::new("docker") .arg("pull") @@ -128,3 +175,26 @@ fn remove_docker_containers(name: &str) { .expect("Failed to execute docker command"); } } + +fn prepare_runtime_dir() -> std::io::Result { + let project_root = { + let mut current_dir = std::env::current_dir().unwrap(); + current_dir.pop(); + current_dir.pop(); + current_dir + }; + + let containers_state_dir = project_root.join(".docker/container-state"); + assert!(containers_state_dir.exists()); + let containers_runtime_dir = project_root.join(".docker/container-runtime"); + + // Remove runtime directory if it exists to copy containers files to a clean directory + if containers_runtime_dir.exists() { + std::fs::remove_dir_all(&containers_runtime_dir).unwrap(); + } + + // Copy container files to runtime directory + mm2_io::fs::copy_dir_all(&containers_state_dir, &containers_runtime_dir).unwrap(); + + Ok(containers_runtime_dir) +} diff --git a/mm2src/mm2_main/tests/docker_tests_sia_unique.rs b/mm2src/mm2_main/tests/docker_tests_sia_unique.rs new file mode 100644 index 0000000000..521da60e01 --- /dev/null +++ b/mm2src/mm2_main/tests/docker_tests_sia_unique.rs @@ -0,0 +1,106 @@ +#![allow(unused_imports, dead_code)] +#![cfg(feature = "enable-sia")] +#![feature(async_closure)] +#![feature(custom_test_frameworks)] +#![feature(test)] +#![test_runner(docker_tests_runner)] +#![feature(drain_filter)] +#![feature(hash_raw_entry)] +#![cfg(not(target_arch = "wasm32"))] + +#[cfg(test)] +#[macro_use] +extern crate common; +#[cfg(test)] +#[macro_use] +extern crate gstuff; +#[cfg(test)] +#[macro_use] +extern crate lazy_static; +#[cfg(test)] +#[macro_use] +extern crate serde_json; +#[cfg(test)] extern crate ser_error_derive; +#[cfg(test)] extern crate test; + +use std::env; +use std::io::{BufRead, BufReader}; +use std::path::PathBuf; +use std::process::Command; +use test::{test_main, StaticBenchFn, StaticTestFn, TestDescAndFn}; +use testcontainers::clients::Cli; + +mod docker_tests; +use docker_tests::docker_tests_common::*; + +#[allow(dead_code)] mod integration_tests_common; + +/// Custom test runner intended to initialize the SIA coin daemon in a Docker container. +pub fn docker_tests_runner(tests: &[&TestDescAndFn]) { + let docker = Cli::default(); + let mut containers = vec![]; + + let skip_docker_tests_runner = std::env::var("SKIP_DOCKER_TESTS_RUNNER") + .map(|v| v == "1") + .unwrap_or(false); + + if !skip_docker_tests_runner { + const IMAGES: &[&str] = &[SIA_DOCKER_IMAGE_WITH_TAG]; + + for image in IMAGES { + pull_docker_image(image); + remove_docker_containers(image); + } + + let sia_node = sia_docker_node(&docker, "SIA", 9980); + println!("ran container?"); + containers.push(sia_node); + } + // detect if docker is installed + // skip the tests that use docker if not installed + let owned_tests: Vec<_> = tests + .iter() + .map(|t| match t.testfn { + StaticTestFn(f) => TestDescAndFn { + testfn: StaticTestFn(f), + desc: t.desc.clone(), + }, + StaticBenchFn(f) => TestDescAndFn { + testfn: StaticBenchFn(f), + desc: t.desc.clone(), + }, + _ => panic!("non-static tests passed to lp_coins test runner"), + }) + .collect(); + let args: Vec = env::args().collect(); + test_main(&args, owned_tests, None); +} + +fn pull_docker_image(name: &str) { + Command::new("docker") + .arg("pull") + .arg(name) + .status() + .expect("Failed to execute docker command"); +} + +fn remove_docker_containers(name: &str) { + let stdout = Command::new("docker") + .arg("ps") + .arg("-f") + .arg(format!("ancestor={}", name)) + .arg("-q") + .output() + .expect("Failed to execute docker command"); + + let reader = BufReader::new(stdout.stdout.as_slice()); + let ids: Vec<_> = reader.lines().map(|line| line.unwrap()).collect(); + if !ids.is_empty() { + Command::new("docker") + .arg("rm") + .arg("-f") + .args(ids) + .status() + .expect("Failed to execute docker command"); + } +} diff --git a/mm2src/mm2_main/tests/integration_tests_common/mod.rs b/mm2src/mm2_main/tests/integration_tests_common/mod.rs index 13393bd8d1..0c22f3c8bd 100644 --- a/mm2src/mm2_main/tests/integration_tests_common/mod.rs +++ b/mm2src/mm2_main/tests/integration_tests_common/mod.rs @@ -2,12 +2,12 @@ use common::executor::Timer; use common::log::LogLevel; use common::{block_on, log, now_ms, wait_until_ms}; use crypto::privkey::key_pair_from_seed; -use mm2_main::mm2::{lp_main, LpMainParams}; +use mm2_main::{lp_main, LpMainParams}; use mm2_rpc::data::legacy::CoinInitResponse; use mm2_test_helpers::electrums::{doc_electrums, marty_electrums}; use mm2_test_helpers::for_tests::{create_new_account_status, enable_native as enable_native_impl, init_create_new_account, MarketMakerIt}; -use mm2_test_helpers::structs::{CreateNewAccountStatus, HDAccountAddressId, HDAccountBalance, InitTaskResult, +use mm2_test_helpers::structs::{CreateNewAccountStatus, HDAccountAddressId, HDAccountBalanceMap, InitTaskResult, RpcV2Response}; use serde_json::{self as json, Value as Json}; use std::collections::HashMap; @@ -99,7 +99,7 @@ pub async fn create_new_account( coin: &str, account_id: Option, timeout: u64, -) -> HDAccountBalance { +) -> HDAccountBalanceMap { let init = init_create_new_account(mm, coin, account_id).await; let init: RpcV2Response = json::from_value(init).unwrap(); let timeout = wait_until_ms(timeout * 1000); diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index 06abd46a92..c8d3252ad4 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -5,7 +5,7 @@ use common::executor::Timer; use common::{cfg_native, cfg_wasm32, log, new_uuid}; use crypto::privkey::key_pair_from_seed; use http::{HeaderMap, StatusCode}; -use mm2_main::mm2::lp_ordermatch::MIN_ORDER_KEEP_ALIVE_INTERVAL; +use mm2_main::lp_ordermatch::MIN_ORDER_KEEP_ALIVE_INTERVAL; use mm2_metrics::{MetricType, MetricsJson}; use mm2_number::{BigDecimal, BigRational, Fraction, MmNumber}; use mm2_rpc::data::legacy::{CoinInitResponse, MmVersionResponse, OrderbookResponse}; @@ -14,15 +14,15 @@ use mm2_test_helpers::electrums::*; use mm2_test_helpers::for_tests::wait_check_stats_swap_status; use mm2_test_helpers::for_tests::{account_balance, btc_segwit_conf, btc_with_spv_conf, btc_with_sync_starting_header, check_recent_swaps, enable_qrc20, enable_utxo_v2_electrum, eth_dev_conf, - find_metrics_in_json, from_env_file, get_new_address, get_shared_db_id, mm_spat, - morty_conf, my_balance, rick_conf, sign_message, start_swaps, tbtc_conf, - tbtc_segwit_conf, tbtc_with_spv_conf, test_qrc20_history_impl, tqrc20_conf, - verify_message, wait_for_swaps_finish_and_check_status, - wait_till_history_has_records, MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, - Mm2TestConfForSwap, RaiiDump, DOC_ELECTRUM_ADDRS, ETH_MAINNET_NODE, - ETH_MAINNET_SWAP_CONTRACT, ETH_SEPOLIA_NODES, ETH_SEPOLIA_SWAP_CONTRACT, - MARTY_ELECTRUM_ADDRS, MORTY, QRC20_ELECTRUMS, RICK, RICK_ELECTRUM_ADDRS, - TBTC_ELECTRUMS, T_BCH_ELECTRUMS}; + find_metrics_in_json, from_env_file, get_new_address, get_shared_db_id, + get_wallet_names, mm_spat, morty_conf, my_balance, rick_conf, sign_message, + start_swaps, tbtc_conf, tbtc_segwit_conf, tbtc_with_spv_conf, + test_qrc20_history_impl, tqrc20_conf, verify_message, + wait_for_swaps_finish_and_check_status, wait_till_history_has_records, + MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, RaiiDump, + DOC_ELECTRUM_ADDRS, ETH_MAINNET_NODE, ETH_MAINNET_SWAP_CONTRACT, ETH_SEPOLIA_NODES, + ETH_SEPOLIA_SWAP_CONTRACT, MARTY_ELECTRUM_ADDRS, MORTY, QRC20_ELECTRUMS, RICK, + RICK_ELECTRUM_ADDRS, TBTC_ELECTRUMS, T_BCH_ELECTRUMS}; use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::*; use serde_json::{self as json, json, Value as Json}; @@ -35,7 +35,7 @@ use uuid::Uuid; cfg_native! { use common::block_on; - use mm2_test_helpers::for_tests::{get_passphrase, new_mm2_temp_folder_path}; + use mm2_test_helpers::for_tests::{get_passphrase, new_mm2_temp_folder_path, peer_connection_healthcheck}; use mm2_io::fs::slurp; use hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN; } @@ -2351,7 +2351,7 @@ fn test_electrum_tx_history() { {"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, ]); - let mut mm = MarketMakerIt::start( + let mm_bob = MarketMakerIt::start( json! ({ "gui": "nogui", "netid": 9998, @@ -2367,14 +2367,26 @@ fn test_electrum_tx_history() { None, ) .unwrap(); + let (_dump_log, _dump_dashboard) = mm_bob.mm_dump(); + log!("log path: {}", mm_bob.log_path.display()); + + let bob_electrum = block_on(enable_electrum(&mm_bob, "RICK", false, DOC_ELECTRUM_ADDRS)); + let mut enable_res_bob = HashMap::new(); + enable_res_bob.insert("RICK", bob_electrum); + log!("enable_coins_bob: {:?}", enable_res_bob); + + let mmconf = Mm2TestConf::seednode_with_wallet_name(&coins, "wallet", "pass"); + let mut mm = MarketMakerIt::start(mmconf.conf, mmconf.rpc_password, None).unwrap(); let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("log path: {}", mm.log_path.display()); // Enable RICK electrum client with tx_history loop. let electrum = block_on(enable_electrum(&mm, "RICK", true, DOC_ELECTRUM_ADDRS)); + log!("enable_coins: {:?}", electrum); + let receiving_address = electrum.address; // Wait till tx_history will not be loaded - block_on(mm.wait_for_log(500., |log| log.contains("history has been loaded successfully"))).unwrap(); + block_on(mm.wait_for_log(5., |log| log.contains("history has been loaded successfully"))).unwrap(); // tx_history is requested every 30 seconds, wait another iteration thread::sleep(Duration::from_secs(31)); @@ -2384,16 +2396,13 @@ fn test_electrum_tx_history() { assert_eq!(get_tx_history_request_count(&mm), 1); // make a transaction to change balance - let mut enable_res = HashMap::new(); - enable_res.insert("RICK", electrum); - log!("enable_coins: {:?}", enable_res); withdraw_and_send( - &mm, + &mm_bob, "RICK", None, - "RRYmiZSDo3UdHHqj1rLKf8cbJroyv9NxXw", - &enable_res, - "-0.00001", + &receiving_address, + &enable_res_bob, + "-0.00101", 0.001, ); @@ -5662,7 +5671,7 @@ fn test_enable_utxo_with_enable_hd() { None, )); let balance = match rick.wallet_balance { - EnableCoinBalance::HD(hd) => hd, + EnableCoinBalanceMap::HD(hd) => hd, _ => panic!("Expected EnableCoinBalance::HD"), }; let account = balance.accounts.get(0).expect("Expected account at index 0"); @@ -5680,7 +5689,7 @@ fn test_enable_utxo_with_enable_hd() { None, )); let balance = match btc_segwit.wallet_balance { - EnableCoinBalance::HD(hd) => hd, + EnableCoinBalanceMap::HD(hd) => hd, _ => panic!("Expected EnableCoinBalance::HD"), }; let account = balance.accounts.get(0).expect("Expected account at index 0"); @@ -5809,6 +5818,41 @@ fn test_get_shared_db_id() { ); } +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_get_wallet_names() { + let coins = json!([]); + + // Initialize the first wallet with a specific name + let wallet_1 = Mm2TestConf::seednode_with_wallet_name(&coins, "wallet_1", "pass"); + let mm_wallet_1 = MarketMakerIt::start(wallet_1.conf, wallet_1.rpc_password, None).unwrap(); + + // Retrieve and verify the wallet names for the first wallet + let get_wallet_names_1 = block_on(get_wallet_names(&mm_wallet_1)); + assert_eq!(get_wallet_names_1.wallet_names, vec!["wallet_1"]); + assert_eq!(get_wallet_names_1.activated_wallet.unwrap(), "wallet_1"); + + // Initialize the second wallet with a different name + let mut wallet_2 = Mm2TestConf::seednode_with_wallet_name(&coins, "wallet_2", "pass"); + + // Set the database directory for the second wallet to the same as the first wallet + wallet_2.conf["dbdir"] = mm_wallet_1.folder.join("DB").to_str().unwrap().into(); + + // Stop the first wallet before starting the second one + block_on(mm_wallet_1.stop()).unwrap(); + + // Start the second wallet + let mm_wallet_2 = MarketMakerIt::start(wallet_2.conf, wallet_2.rpc_password, None).unwrap(); + + // Retrieve and verify the wallet names for the second wallet + let get_wallet_names_2 = block_on(get_wallet_names(&mm_wallet_2)); + assert_eq!(get_wallet_names_2.wallet_names, vec!["wallet_1", "wallet_2"]); + assert_eq!(get_wallet_names_2.activated_wallet.unwrap(), "wallet_2"); + + // Stop the second wallet + block_on(mm_wallet_2.stop()).unwrap(); +} + #[test] #[cfg(not(target_arch = "wasm32"))] fn test_sign_raw_transaction_rick() { @@ -5919,6 +5963,46 @@ fn test_sign_raw_transaction_p2wpkh() { assert!(response["error"].as_str().unwrap().contains("Signing error")); } +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_connection_healthcheck_rpc() { + const BOB_ADDRESS: &str = "12D3KooWEtuv7kmgGCC7oAQ31hB7AR5KkhT3eEWB2bP2roo3M7rY"; + const BOB_SEED: &str = "dummy-value-bob"; + + const ALICE_ADDRESS: &str = "12D3KooWHnoKd2Lr7BoxHCCeBhcnfAZsdiCdojbEMLE7DDSbMo1g"; + const ALICE_SEED: &str = "dummy-value-alice"; + + let bob_conf = Mm2TestConf::seednode(BOB_SEED, &json!([])); + let bob_mm = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); + + thread::sleep(Duration::from_secs(2)); + + let mut alice_conf = Mm2TestConf::seednode(ALICE_SEED, &json!([])); + alice_conf.conf["seednodes"] = json!([bob_mm.my_seed_addr()]); + alice_conf.conf["skip_startup_checks"] = json!(true); + let alice_mm = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); + + thread::sleep(Duration::from_secs(2)); + + // Self-address check for Bob + let response = block_on(peer_connection_healthcheck(&bob_mm, BOB_ADDRESS)); + assert_eq!(response["result"], json!(true)); + + // Check address of Alice + let response = block_on(peer_connection_healthcheck(&bob_mm, ALICE_ADDRESS)); + assert_eq!(response["result"], json!(true)); + + thread::sleep(Duration::from_secs(1)); + + // Self-address check for Alice + let response = block_on(peer_connection_healthcheck(&alice_mm, ALICE_ADDRESS)); + assert_eq!(response["result"], json!(true)); + + // Check address of Bob + let response = block_on(peer_connection_healthcheck(&alice_mm, BOB_ADDRESS)); + assert_eq!(response["result"], json!(true)); +} + #[cfg(all(feature = "run-device-tests", not(target_arch = "wasm32")))] mod trezor_tests { use coins::eth::{eth_coin_from_conf_and_request, gas_limit, EthCoin}; @@ -5937,8 +6021,8 @@ mod trezor_tests { use crypto::hw_rpc_task::HwRpcTaskAwaitingStatus; use crypto::CryptoCtx; use mm2_core::mm_ctx::MmArc; - use mm2_main::mm2::init_hw::init_trezor_user_action; - use mm2_main::mm2::init_hw::{init_trezor, init_trezor_status, InitHwRequest, InitHwResponse}; + use mm2_main::init_hw::init_trezor_user_action; + use mm2_main::init_hw::{init_trezor, init_trezor_status, InitHwRequest, InitHwResponse}; use mm2_test_helpers::electrums::tbtc_electrums; use mm2_test_helpers::for_tests::{enable_utxo_v2_electrum, eth_sepolia_trezor_firmware_compat_conf, eth_testnet_conf_trezor, init_trezor_rpc, init_trezor_status_rpc, diff --git a/mm2src/mm2_main/tests/mm2_tests/mod.rs b/mm2src/mm2_main/tests/mm2_tests/mod.rs index cfc3aa1e15..6bd10e28a2 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mod.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mod.rs @@ -5,8 +5,6 @@ mod lightning_tests; mod lp_bot_tests; mod mm2_tests_inner; mod orderbook_sync_tests; -mod tendermint_ibc_asset_tests; -mod tendermint_tests; mod z_coin_tests; mod zhtlc_native_reexport { diff --git a/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs b/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs index bc3ec5e93d..187404a580 100644 --- a/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs @@ -1,11 +1,11 @@ use crate::integration_tests_common::{enable_coins_rick_morty_electrum, enable_electrum, enable_electrum_json}; use common::{block_on, log}; use http::StatusCode; -use mm2_main::mm2::lp_ordermatch::MIN_ORDER_KEEP_ALIVE_INTERVAL; +use mm2_main::lp_ordermatch::MIN_ORDER_KEEP_ALIVE_INTERVAL; use mm2_number::{BigDecimal, BigRational, MmNumber}; use mm2_rpc::data::legacy::{AggregatedOrderbookEntry, CoinInitResponse, OrderbookResponse}; use mm2_test_helpers::electrums::doc_electrums; -use mm2_test_helpers::for_tests::{enable_z_coin_light, get_passphrase, morty_conf, orderbook_v2, rick_conf, +use mm2_test_helpers::for_tests::{enable_z_coin_light, get_passphrase, morty_conf, orderbook_v2, rick_conf, set_price, zombie_conf, MarketMakerIt, Mm2TestConf, DOC_ELECTRUM_ADDRS, MARTY_ELECTRUM_ADDRS, RICK, ZOMBIE_ELECTRUMS, ZOMBIE_LIGHTWALLETD_URLS, ZOMBIE_TICKER}; use mm2_test_helpers::get_passphrase; @@ -1308,6 +1308,79 @@ fn setprice_min_volume_should_be_displayed_in_orderbook() { assert_eq!(min_volume, "1", "Alice MORTY/RICK ask must display correct min_volume"); } +#[test] +fn test_order_cancellation_received_before_creation() { + let coins = json!([rick_conf(), morty_conf()]); + + let bob_passphrase = "bob passphrase"; + let mm_bob_conf = Mm2TestConf::seednode(bob_passphrase, &coins); + let mm_bob = MarketMakerIt::start(mm_bob_conf.conf, mm_bob_conf.rpc_password, None).unwrap(); + let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); + log!("Bob log path: {}", mm_bob.log_path.display()); + log!( + "enable_coins (bob): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_bob)) + ); + + // Bob places maker order before Alice connects to the network so that Alice receives the order creation through IHAVE/IWANT messages. + let set_price = block_on(set_price(&mm_bob, "RICK", "MORTY", "0.9", "0.9", false)); + + let mm_alice_conf = Mm2TestConf::light_node("alice passphrase", &coins, &[&mm_bob.ip.to_string()]); + let mut mm_alice = MarketMakerIt::start(mm_alice_conf.conf, mm_alice_conf.rpc_password, None).unwrap(); + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + log!( + "enable_coins (alice): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_alice)) + ); + + // Alice requests the orderbook to subscribe to the topic + let orderbook = block_on(orderbook_v2(&mm_alice, "RICK", "MORTY")); + let asks = orderbook["result"]["asks"].as_array().unwrap(); + // Alice should see the order in the orderbook as she got it through `GetOrderbook` p2p request. + assert_eq!(asks.len(), 1, "Alice RICK/MORTY orderbook must have exactly 1 ask"); + + // Bob cancels the order straight after Alice subscribes to the orderbook topic + // so that Alice receives the cancellation message faster than the creation message + // that she will receive later through IHAVE/IWANT messages. + let cancel_orders = block_on(mm_bob.rpc(&json! ({ + "userpass": mm_bob.userpass, + "method": "cancel_all_orders", + "cancel_by": { + "type": "All", + } + }))) + .unwrap(); + assert!(cancel_orders.0.is_success(), "!cancel_all_orders: {}", cancel_orders.1); + + // Make sure Alice receives the order cancellation message. + block_on(mm_alice.wait_for_log(10., |log| { + log.contains("received ordermatch message MakerOrderCancelled") + })) + .unwrap(); + + // Make sure Alice receives the order creation message. + block_on(mm_alice.wait_for_log(10., |log| log.contains("received ordermatch message MakerOrderCreated"))).unwrap(); + + // Make sure Alice ignores inserting of the order into the orderbook. + block_on(mm_alice.wait_for_log(10., |log| { + log.contains(&format!( + "Maker order {} was recently cancelled, ignoring", + set_price.result.uuid + )) + })) + .unwrap(); + + // Check Alice's orderbook again to make sure the order wasn't re-inserted after the cancellation. + let orderbook = block_on(orderbook_v2(&mm_alice, "RICK", "MORTY")); + let asks = orderbook["result"]["asks"].as_array().unwrap(); + // Alice shouldn't find the order in the orderbook. + assert_eq!(asks.len(), 0, "Alice RICK/MORTY orderbook must have exactly 0 ask"); + + block_on(mm_bob.stop()).unwrap(); + block_on(mm_alice.stop()).unwrap(); +} + // ignored because it requires a long-running ZOMBIE initialization process #[test] #[ignore] diff --git a/mm2src/mm2_main/tests/mm2_tests/tendermint_ibc_asset_tests.rs b/mm2src/mm2_main/tests/mm2_tests/tendermint_ibc_asset_tests.rs deleted file mode 100644 index a568073664..0000000000 --- a/mm2src/mm2_main/tests/mm2_tests/tendermint_ibc_asset_tests.rs +++ /dev/null @@ -1,99 +0,0 @@ -use common::{block_on, log}; -use mm2_number::BigDecimal; -use mm2_rpc::data::legacy::OrderbookResponse; -use mm2_test_helpers::for_tests::{enable_tendermint, enable_tendermint_without_balance, iris_testnet_conf, my_balance, - orderbook, orderbook_v2, set_price, usdc_ibc_iris_testnet_conf, MarketMakerIt, - Mm2TestConf}; -use mm2_test_helpers::structs::{OrderbookAddress, OrderbookV2Response, RpcV2Response, TendermintActivationResult}; - -use serde_json::{self, json}; -use std::collections::HashSet; -use std::iter::FromIterator; - -const IRIS_TESTNET_RPCS: &[&str] = &["http://34.80.202.172:26657"]; -const IRIS_TICKER: &str = "IRIS-TEST"; -const USDC_IBC_TICKER: &str = "USDC-IBC-IRIS"; -const IRIS_USDC_ACTIVATION_SEED: &str = "iris usdc activation"; - -#[test] -fn test_iris_with_usdc_activation_balance_orderbook() { - let coins = json!([iris_testnet_conf(), usdc_ibc_iris_testnet_conf()]); - - let conf = Mm2TestConf::seednode(IRIS_USDC_ACTIVATION_SEED, &coins); - let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - - let activation_result = block_on(enable_tendermint( - &mm, - IRIS_TICKER, - &[USDC_IBC_TICKER], - IRIS_TESTNET_RPCS, - false, - )); - - let response: RpcV2Response = serde_json::from_value(activation_result).unwrap(); - - let expected_address = "iaa1udqnpvaw3uyv3gsl7m6800wyask5wj7quvd4nm"; - assert_eq!(response.result.address, expected_address); - - let expected_iris_balance = BigDecimal::from(100); - assert_eq!(response.result.balance.unwrap().spendable, expected_iris_balance); - - let expected_usdc_balance: BigDecimal = "0.683142".parse().unwrap(); - - let tokens_balances = response.result.tokens_balances.unwrap(); - let actual_usdc_balance = tokens_balances.get(USDC_IBC_TICKER).unwrap(); - assert_eq!(actual_usdc_balance.spendable, expected_usdc_balance); - - let actual_usdc_balance = block_on(my_balance(&mm, USDC_IBC_TICKER)).balance; - assert_eq!(actual_usdc_balance, expected_usdc_balance); - - let set_price_res = block_on(set_price(&mm, USDC_IBC_TICKER, IRIS_TICKER, "1", "0.1", false)); - log!("{:?}", set_price_res); - - let set_price_res = block_on(set_price(&mm, IRIS_TICKER, USDC_IBC_TICKER, "1", "0.1", false)); - log!("{:?}", set_price_res); - - let orderbook = block_on(orderbook(&mm, USDC_IBC_TICKER, IRIS_TICKER)); - let orderbook: OrderbookResponse = serde_json::from_value(orderbook).unwrap(); - - let first_ask = orderbook.asks.first().unwrap(); - assert_eq!(first_ask.entry.address, expected_address); - - let first_bid = orderbook.bids.first().unwrap(); - assert_eq!(first_bid.entry.address, expected_address); - - let orderbook_v2 = block_on(orderbook_v2(&mm, USDC_IBC_TICKER, IRIS_TICKER)); - let orderbook_v2: RpcV2Response = serde_json::from_value(orderbook_v2).unwrap(); - - let expected_address = OrderbookAddress::Transparent(expected_address.into()); - let first_ask = orderbook_v2.result.asks.first().unwrap(); - assert_eq!(first_ask.entry.address, expected_address); - - let first_bid = orderbook_v2.result.bids.first().unwrap(); - assert_eq!(first_bid.entry.address, expected_address); -} - -#[test] -fn test_iris_with_usdc_activation_without_balance() { - let coins = json!([iris_testnet_conf(), usdc_ibc_iris_testnet_conf()]); - - let conf = Mm2TestConf::seednode(IRIS_USDC_ACTIVATION_SEED, &coins); - let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - - let activation_result = block_on(enable_tendermint_without_balance( - &mm, - IRIS_TICKER, - &[USDC_IBC_TICKER], - IRIS_TESTNET_RPCS, - false, - )); - - let result: RpcV2Response = serde_json::from_value(activation_result).unwrap(); - - assert!(result.result.balance.is_none()); - assert!(result.result.tokens_balances.is_none()); - assert_eq!( - result.result.tokens_tickers.unwrap(), - HashSet::from_iter(vec![USDC_IBC_TICKER.to_string()]) - ); -} diff --git a/mm2src/mm2_net/Cargo.toml b/mm2src/mm2_net/Cargo.toml index 844f07e1f3..a720327d96 100644 --- a/mm2src/mm2_net/Cargo.toml +++ b/mm2src/mm2_net/Cargo.toml @@ -7,11 +7,9 @@ edition = "2018" doctest = false [features] -event-stream = ["mm2_event_stream", "async-stream" , "p2p"] -p2p = ["mm2-libp2p", "parking_lot"] [dependencies] -async-stream = { version = "0.3", optional = true } +async-stream = { version = "0.3" } async-trait = "0.1" bytes = "1.1" cfg-if = "1.0" @@ -19,14 +17,13 @@ common = { path = "../common" } derive_more = "0.99" ethkey = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git", rev = "mm2-v2.1.1" } futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } +gstuff = "0.7" http = "0.2" lazy_static = "1.4" mm2_core = { path = "../mm2_core" } mm2_err_handle = { path = "../mm2_err_handle" } -mm2_event_stream = { path = "../mm2_event_stream", optional = true } -mm2-libp2p = { path = "../mm2_p2p", package = "mm2_p2p", optional = true } -parking_lot = { version = "0.12.0", features = ["nightly"], optional = true } -prost = "0.11" +mm2_number = { path = "../mm2_number" } +prost = "0.12" rand = { version = "0.7", features = ["std", "small_rng", "wasm-bindgen"] } serde = "1" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } @@ -35,13 +32,12 @@ thiserror = "1.0.30" [target.'cfg(target_arch = "wasm32")'.dependencies] base64 = "0.21.7" futures-util = "0.3" -gstuff = { version = "0.7", features = ["nightly"] } mm2_state_machine = { path = "../mm2_state_machine"} http-body = "0.4" httparse = "1.8.0" js-sys = "0.3.27" pin-project = "1.1.2" -tonic = { version = "0.9", default-features = false, features = ["prost", "codegen"] } +tonic = { version = "0.10", default-features = false, features = ["prost", "codegen"] } tower-service = "0.3" wasm-bindgen = "0.2.86" wasm-bindgen-test = { version = "0.3.2" } @@ -55,7 +51,6 @@ web-sys = { version = "0.3.55", features = ["console", "CloseEvent", "DomExcepti [target.'cfg(not(target_arch = "wasm32"))'.dependencies] futures-util = { version = "0.3" } hyper = { version = "0.14.26", features = ["client", "http2", "server", "tcp", "stream"] } -gstuff = { version = "0.7", features = ["nightly"] } rustls = { version = "0.21", default-features = false } tokio = { version = "1.20" } tokio-rustls = { version = "0.24", default-features = false } diff --git a/mm2src/mm2_net/src/lib.rs b/mm2src/mm2_net/src/lib.rs index 954e25c5a0..4ae26ca182 100644 --- a/mm2src/mm2_net/src/lib.rs +++ b/mm2src/mm2_net/src/lib.rs @@ -1,13 +1,9 @@ pub mod grpc_web; -#[cfg(feature = "event-stream")] pub mod network_event; -#[cfg(feature = "p2p")] pub mod p2p; pub mod transport; #[cfg(not(target_arch = "wasm32"))] pub mod ip_addr; #[cfg(not(target_arch = "wasm32"))] pub mod native_http; #[cfg(not(target_arch = "wasm32"))] pub mod native_tls; -#[cfg(all(feature = "event-stream", not(target_arch = "wasm32")))] -pub mod sse_handler; +#[cfg(not(target_arch = "wasm32"))] pub mod sse_handler; #[cfg(target_arch = "wasm32")] pub mod wasm; -#[cfg(all(feature = "event-stream", target_arch = "wasm32"))] -pub mod wasm_event_stream; +#[cfg(target_arch = "wasm32")] pub mod wasm_event_stream; diff --git a/mm2src/mm2_net/src/native_http.rs b/mm2src/mm2_net/src/native_http.rs index 924b4e8448..94f37ef65c 100644 --- a/mm2src/mm2_net/src/native_http.rs +++ b/mm2src/mm2_net/src/native_http.rs @@ -13,7 +13,6 @@ use async_trait::async_trait; use futures::channel::oneshot::Canceled; -use http::header::ACCEPT; use http::{header, HeaderValue, Request}; use hyper::client::connect::Connect; use hyper::client::ResponseFuture; @@ -21,7 +20,7 @@ use hyper::{Body, Client}; use serde_json::Value as Json; use common::wio::{drive03, HYPER}; -use common::APPLICATION_JSON; +use common::{APPLICATION_JSON, X_AUTH_PAYLOAD}; use mm2_err_handle::prelude::*; use super::transport::{GetInfoFromUriError, SlurpError, SlurpResult, SlurpResultJson}; @@ -158,8 +157,7 @@ pub trait SlurpHttpClient { let body_bytes = hyper::body::to_bytes(response.into_body()) .await .map_to_mm(|e| SlurpError::from_hyper_error(e, uri.clone()))?; - let body_str = String::from_utf8(body_bytes.to_vec()).map_to_mm(|e| SlurpError::Internal(e.to_string()))?; - let body: Json = serde_json::from_str(&body_str)?; + let body: Json = serde_json::from_slice(&body_bytes)?; Ok((status, headers, body)) } @@ -237,12 +235,15 @@ impl From for SlurpError { /// # Errors /// /// Returns an error if the HTTP status code of the response is not in the 2xx range. -pub async fn send_request_to_uri(uri: &str) -> MmResult { - let request = http::Request::builder() +pub async fn send_request_to_uri(uri: &str, auth_header: Option<&str>) -> MmResult { + let mut request_builder = http::Request::builder() .method("GET") .uri(uri) - .header(ACCEPT, HeaderValue::from_static(APPLICATION_JSON)) - .body(hyper::Body::from(""))?; + .header(header::ACCEPT, HeaderValue::from_static(APPLICATION_JSON)); + if let Some(auth_header) = auth_header { + request_builder = request_builder.header(X_AUTH_PAYLOAD, HeaderValue::from_str(auth_header)?); + } + let request = request_builder.body(Body::empty())?; let (status, _header, body) = slurp_req_body(request).await?; if !status.is_success() { diff --git a/mm2src/mm2_net/src/sse_handler.rs b/mm2src/mm2_net/src/sse_handler.rs index f57018ca04..568bfc98c0 100644 --- a/mm2src/mm2_net/src/sse_handler.rs +++ b/mm2src/mm2_net/src/sse_handler.rs @@ -1,12 +1,11 @@ use hyper::{body::Bytes, Body, Request, Response}; use mm2_core::mm_ctx::MmArc; use serde_json::json; -use std::convert::Infallible; pub const SSE_ENDPOINT: &str = "/event-stream"; /// Handles broadcasted messages from `mm2_event_stream` continuously. -pub async fn handle_sse(request: Request, ctx_h: u32) -> Result, Infallible> { +pub async fn handle_sse(request: Request, ctx_h: u32) -> Response { // This is only called once for per client on the initialization, // meaning this is not a resource intensive computation. let ctx = match MmArc::from_ffi_handle(ctx_h) { @@ -62,17 +61,15 @@ pub async fn handle_sse(request: Request, ctx_h: u32) -> Result Ok(res), - Err(err) => return handle_internal_error(err.to_string()).await, + Ok(res) => res, + Err(err) => handle_internal_error(err.to_string()).await, } } /// Fallback function for handling errors in SSE connections -async fn handle_internal_error(message: String) -> Result, Infallible> { - let response = Response::builder() +async fn handle_internal_error(message: String) -> Response { + Response::builder() .status(500) .body(Body::from(message)) - .expect("Returning 500 should never fail."); - - Ok(response) + .expect("Returning 500 should never fail.") } diff --git a/mm2src/mm2_net/src/transport.rs b/mm2src/mm2_net/src/transport.rs index 3774001b35..750d2561c2 100644 --- a/mm2src/mm2_net/src/transport.rs +++ b/mm2src/mm2_net/src/transport.rs @@ -1,6 +1,5 @@ use common::jsonrpc_client::JsonRpcErrorType; use derive_more::Display; -use ethkey::Secret; use http::{HeaderMap, StatusCode}; use mm2_err_handle::prelude::*; use serde::{Deserialize, Serialize}; @@ -69,22 +68,6 @@ where }) } -#[derive(Clone, Debug)] -pub struct GuiAuthValidationGenerator { - pub coin_ticker: String, - pub secret: Secret, - pub address: String, -} - -/// gui-auth specific data-type that needed in order to perform gui-auth calls -#[derive(Clone, Serialize)] -pub struct GuiAuthValidation { - pub coin_ticker: String, - pub address: String, - pub timestamp_message: i64, - pub signature: String, -} - /// Errors encountered when making HTTP requests to fetch information from a URI. #[derive(Clone, Debug, Deserialize, Display, PartialEq, Serialize)] pub enum GetInfoFromUriError { @@ -119,6 +102,11 @@ impl From for GetInfoFromUriError { } } +#[cfg(not(target_arch = "wasm32"))] +impl From for GetInfoFromUriError { + fn from(e: hyper::header::InvalidHeaderValue) -> Self { GetInfoFromUriError::Internal(e.to_string()) } +} + /// Sends a POST request to the given URI and expects a 2xx status code in response. /// /// # Errors diff --git a/mm2src/mm2_net/src/wasm/http.rs b/mm2src/mm2_net/src/wasm/http.rs index e836da8c68..4795af346c 100644 --- a/mm2src/mm2_net/src/wasm/http.rs +++ b/mm2src/mm2_net/src/wasm/http.rs @@ -1,7 +1,7 @@ use crate::transport::{GetInfoFromUriError, SlurpError, SlurpResult}; use crate::wasm::body_stream::ResponseBody; use common::executor::spawn_local; -use common::{drop_mutability, stringify_js_error, APPLICATION_JSON}; +use common::{drop_mutability, stringify_js_error, APPLICATION_JSON, X_AUTH_PAYLOAD}; use futures::channel::oneshot; use gstuff::ERRL; use http::header::{ACCEPT, CONTENT_TYPE}; @@ -384,7 +384,7 @@ impl RequestBody { /// # Errors /// /// Returns an error if the HTTP status code of the response is not in the 2xx range. -pub async fn send_request_to_uri(uri: &str) -> MmResult { +pub async fn send_request_to_uri(uri: &str, auth_header: Option<&str>) -> MmResult { macro_rules! try_or { ($exp:expr, $errtype:ident) => { match $exp { @@ -394,10 +394,12 @@ pub async fn send_request_to_uri(uri: &str) -> MmResult; +pub type WsOutgoingReceiver = mpsc::Receiver>; pub type WsIncomingSender = mpsc::Sender<(ConnIdx, WebSocketEvent)>; type WsTransportReceiver = mpsc::Receiver; @@ -69,14 +69,14 @@ impl InitWsError { } } -/// The `WsEventReceiver` wrapper that filters and maps the incoming `WebSocketEvent` events into `Result`. +/// The `WsEventReceiver` wrapper that filters and maps the incoming `WebSocketEvent` events into `Result, WebSocketError>`. pub struct WsIncomingReceiver { inner: WsEventReceiver, closed: bool, } impl Stream for WsIncomingReceiver { - type Item = Result; + type Item = Result, WebSocketError>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if self.closed { @@ -122,7 +122,7 @@ impl Stream for WsEventReceiver { #[derive(Debug, Clone)] pub struct WsOutgoingSender { - inner: mpsc::Sender, + inner: mpsc::Sender>, /// Is used to determine when all senders are dropped. #[allow(dead_code)] shutdown_tx: OutgoingShutdownTx, @@ -132,9 +132,9 @@ pub struct WsOutgoingSender { /// Please note `WsOutgoingSender` must not provide a way to close the [`WsOutgoingSender::inner`] channel, /// because the shutdown_tx wouldn't be closed properly. impl WsOutgoingSender { - pub async fn send(&mut self, msg: Json) -> Result<(), SendError> { self.inner.send(msg).await } + pub async fn send(&mut self, msg: Vec) -> Result<(), SendError> { self.inner.send(msg).await } - pub fn try_send(&mut self, msg: Json) -> Result<(), TrySendError> { self.inner.try_send(msg) } + pub fn try_send(&mut self, msg: Vec) -> Result<(), TrySendError>> { self.inner.try_send(msg) } } #[derive(Debug)] @@ -147,12 +147,12 @@ pub enum WebSocketEvent { /// Please note some of the errors lead to the connection close. Error(WebSocketError), /// A message has been received through a WebSocket connection. - Incoming(Json), + Incoming(Vec), } #[derive(Debug)] pub enum WebSocketError { - OutgoingError { reason: OutgoingError, outgoing: Json }, + OutgoingError { reason: OutgoingError, outgoing: Vec }, InvalidIncoming { description: String }, } @@ -212,6 +212,7 @@ fn spawn_ws_transport( ) -> InitWsResult<(WsOutgoingSender, WsEventReceiver)> { let (ws, ws_transport_rx) = WebSocketImpl::init(url)?; let (incoming_tx, incoming_rx, incoming_shutdown) = incoming_channel(1024); + let (outgoing_tx, outgoing_rx, outgoing_shutdown) = outgoing_channel(1024); let user_shutdown = into_one_shutdown(incoming_shutdown, outgoing_shutdown); @@ -353,17 +354,11 @@ impl WebSocketImpl { Ok((WebSocketImpl { ws, closures }, rx)) } - fn send_to_ws(&self, outgoing: Json) -> Result<(), WebSocketError> { - match json::to_string(&outgoing) { - Ok(req) => self.ws.send_with_str(&req).map_err(|error| { - let reason = OutgoingError::UnderlyingError(stringify_js_error(&error)); - WebSocketError::OutgoingError { reason, outgoing } - }), - Err(e) => { - let reason = OutgoingError::SerializingError(e.to_string()); - Err(WebSocketError::OutgoingError { reason, outgoing }) - }, - } + fn send_to_ws(&self, outgoing: Vec) -> Result<(), WebSocketError> { + self.ws.send_with_u8_array(&outgoing).map_err(|error| { + let reason = OutgoingError::UnderlyingError(stringify_js_error(&error)); + WebSocketError::OutgoingError { reason, outgoing } + }) } fn validate_websocket_url(url: &str) -> Result<(), MmError> { @@ -423,7 +418,7 @@ impl WsStateMachine { } } - fn send_unexpected_outgoing_back(&mut self, outgoing: Json, current_state: &str) { + fn send_unexpected_outgoing_back(&mut self, outgoing: Vec, current_state: &str) { error!( "Unexpected outgoing message while the socket idx={} state is {}", self.idx, current_state @@ -478,7 +473,7 @@ enum StateEvent { /// All instances of `WsOutgoingSender` and `WsIncomingReceiver` were dropped. UserSideClosed, /// Received an outgoing message. It should be forwarded to `WebSocket`. - OutgoingMessage(Json), + OutgoingMessage(Vec), /// Received a `WsTransportEvent` event. It might be an incoming message from `WebSocket` or something else. WsTransportEvent(WsTransportEvent), } @@ -491,7 +486,7 @@ enum WsTransportEvent { code: u16, }, Error(WsTransportError), - Incoming(Json), + Incoming(Vec), } #[derive(Debug)] @@ -565,8 +560,8 @@ impl State for ConnectingState { } }, StateEvent::WsTransportEvent(WsTransportEvent::Incoming(incoming)) => error!( - "Unexpected incoming message {} while the socket idx={} state is ConnectingState", - ctx.idx, incoming + "Unexpected incoming message {:?} while the socket idx={} state is ConnectingState", + incoming, ctx.idx ), } } @@ -647,11 +642,11 @@ impl ClosedState { } } -fn decode_incoming(incoming: MessageEvent) -> Result { +fn decode_incoming(incoming: MessageEvent) -> Result, String> { match incoming.data().dyn_into::() { Ok(txt) => { let txt = String::from(txt); - json::from_str(&txt).map_err(|e| format!("Error deserializing an incoming payload: {}", e)) + Ok(txt.into_bytes()) }, Err(e) => Err(format!("Unknown MessageEvent {:?}", e)), } @@ -724,10 +719,12 @@ mod tests { "method": "server.version", "params": ["1.2", "1.4"], }); + let get_version = json::to_vec(&get_version).expect("Vec serialization won't fail"); outgoing_tx.send(get_version).await.expect("!outgoing_tx.send"); match incoming_rx.next().timeout_secs(5.).await.unwrap_w() { Some((_conn_idx, WebSocketEvent::Incoming(response))) => { + let response: Json = json::from_slice(&response).expect("Failed to parse incoming message"); debug!("Response: {:?}", response); assert!(response.get("result").is_some()); }, diff --git a/mm2src/mm2_number/src/lib.rs b/mm2src/mm2_number/src/lib.rs index 6d1d20f1ca..ba8b520ec0 100644 --- a/mm2src/mm2_number/src/lib.rs +++ b/mm2src/mm2_number/src/lib.rs @@ -13,7 +13,7 @@ pub use num_bigint; pub use num_rational; pub use bigdecimal::BigDecimal; -pub use num_bigint::{BigInt, BigUint}; +pub use num_bigint::{BigInt, BigUint, ParseBigIntError}; pub use num_rational::BigRational; pub use paste::paste; diff --git a/mm2src/mm2_p2p/Cargo.toml b/mm2src/mm2_p2p/Cargo.toml index 02d0415239..6b7f43e7f4 100644 --- a/mm2src/mm2_p2p/Cargo.toml +++ b/mm2src/mm2_p2p/Cargo.toml @@ -3,6 +3,10 @@ name = "mm2_p2p" version = "0.1.0" edition = "2021" +[features] +default = [] +application = ["dep:mm2_number"] + [lib] doctest = false @@ -15,12 +19,17 @@ futures-ticker = "0.0.3" hex = "0.4.2" lazy_static = "1.4" log = "0.4" +mm2_core = { path = "../mm2_core" } +mm2_event_stream = { path = "../mm2_event_stream" } +mm2_number = { path = "../mm2_number", optional = true } +parking_lot = { version = "0.12.0", features = ["nightly"] } rand = { version = "0.7", default-features = false, features = ["wasm-bindgen"] } regex = "1" rmp-serde = "0.14.3" secp256k1 = { version = "0.20", features = ["rand"] } serde = { version = "1.0", default-features = false } serde_bytes = "0.11.5" +serde_json = { version = "1", features = ["preserve_order", "raw_value"] } sha2 = "0.10" smallvec = "1.6.1" syn = "2.0.18" @@ -29,13 +38,13 @@ void = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] futures-rustls = "0.24" instant = "0.1.12" -libp2p = { git = "https://github.com/KomodoPlatform/rust-libp2p.git", tag = "k-0.52.2", default-features = false, features = ["dns", "identify", "floodsub", "gossipsub", "noise", "ping", "request-response", "secp256k1", "tcp", "tokio", "websocket", "macros", "yamux"] } +libp2p = { git = "https://github.com/KomodoPlatform/rust-libp2p.git", tag = "k-0.52.4", default-features = false, features = ["dns", "identify", "floodsub", "gossipsub", "noise", "ping", "request-response", "secp256k1", "tcp", "tokio", "websocket", "macros", "yamux"] } tokio = { version = "1.20", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] futures-rustls = "0.22" instant = { version = "0.1.12", features = ["wasm-bindgen"] } -libp2p = { git = "https://github.com/KomodoPlatform/rust-libp2p.git", tag = "k-0.52.2", default-features = false, features = ["identify", "floodsub", "noise", "gossipsub", "ping", "request-response", "secp256k1", "wasm-ext", "wasm-ext-websocket", "macros", "yamux"] } +libp2p = { git = "https://github.com/KomodoPlatform/rust-libp2p.git", tag = "k-0.52.4", default-features = false, features = ["identify", "floodsub", "noise", "gossipsub", "ping", "request-response", "secp256k1", "wasm-ext", "wasm-ext-websocket", "macros", "yamux"] } [dev-dependencies] async-std = "1.6.2" diff --git a/mm2src/mm2_p2p/src/application/mod.rs b/mm2src/mm2_p2p/src/application/mod.rs new file mode 100644 index 0000000000..bccb70ac5c --- /dev/null +++ b/mm2src/mm2_p2p/src/application/mod.rs @@ -0,0 +1,5 @@ +//! This module contains KDF application logic related to P2P network gated +//! by the "application" feature. + +pub mod network_event; +pub mod request_response; diff --git a/mm2src/mm2_net/src/network_event.rs b/mm2src/mm2_p2p/src/application/network_event.rs similarity index 78% rename from mm2src/mm2_net/src/network_event.rs rename to mm2src/mm2_p2p/src/application/network_event.rs index b88655f383..c3c0a0eb5c 100644 --- a/mm2src/mm2_net/src/network_event.rs +++ b/mm2src/mm2_p2p/src/application/network_event.rs @@ -1,12 +1,11 @@ -use crate::p2p::P2PContext; use async_trait::async_trait; use common::{executor::{SpawnFuture, Timer}, log::info}; use futures::channel::oneshot::{self, Receiver, Sender}; + use mm2_core::mm_ctx::MmArc; pub use mm2_event_stream::behaviour::EventBehaviour; use mm2_event_stream::{behaviour::EventInitStatus, Event, EventName, EventStreamConfiguration}; -use mm2_libp2p::behaviours::atomicdex; use serde_json::json; pub struct NetworkEvent { @@ -22,7 +21,7 @@ impl EventBehaviour for NetworkEvent { fn event_name() -> EventName { EventName::NETWORK } async fn handle(self, interval: f64, tx: oneshot::Sender) { - let p2p_ctx = P2PContext::fetch_from_mm_arc(&self.ctx); + let p2p_ctx = crate::p2p_ctx::P2PContext::fetch_from_mm_arc(&self.ctx); let mut previously_sent = json!({}); tx.send(EventInitStatus::Success).unwrap(); @@ -30,14 +29,14 @@ impl EventBehaviour for NetworkEvent { loop { let p2p_cmd_tx = p2p_ctx.cmd_tx.lock().clone(); - let peers_info = atomicdex::get_peers_info(p2p_cmd_tx.clone()).await; - let gossip_mesh = atomicdex::get_gossip_mesh(p2p_cmd_tx.clone()).await; - let gossip_peer_topics = atomicdex::get_gossip_peer_topics(p2p_cmd_tx.clone()).await; - let gossip_topic_peers = atomicdex::get_gossip_topic_peers(p2p_cmd_tx.clone()).await; - let relay_mesh = atomicdex::get_relay_mesh(p2p_cmd_tx).await; + let directly_connected_peers = crate::get_directly_connected_peers(p2p_cmd_tx.clone()).await; + let gossip_mesh = crate::get_gossip_mesh(p2p_cmd_tx.clone()).await; + let gossip_peer_topics = crate::get_gossip_peer_topics(p2p_cmd_tx.clone()).await; + let gossip_topic_peers = crate::get_gossip_topic_peers(p2p_cmd_tx.clone()).await; + let relay_mesh = crate::get_relay_mesh(p2p_cmd_tx).await; let event_data = json!({ - "peers_info": peers_info, + "directly_connected_peers": directly_connected_peers, "gossip_mesh": gossip_mesh, "gossip_peer_topics": gossip_peer_topics, "gossip_topic_peers": gossip_topic_peers, diff --git a/mm2src/mm2_p2p/src/application/request_response/mod.rs b/mm2src/mm2_p2p/src/application/request_response/mod.rs new file mode 100644 index 0000000000..28da482bdc --- /dev/null +++ b/mm2src/mm2_p2p/src/application/request_response/mod.rs @@ -0,0 +1,21 @@ +//! This module defines types exclusively for the request-response P2P protocol +//! which are separate from other request types such as RPC requests or Gossipsub +//! messages. + +pub mod network_info; +pub mod ordermatch; + +use serde::{Deserialize, Serialize}; + +/// Wrapper type for handling request-response P2P requests. +#[derive(Eq, Debug, Deserialize, PartialEq, Serialize)] +pub enum P2PRequest { + /// Request for order matching. + Ordermatch(ordermatch::OrdermatchRequest), + /// Request for network information from the target peer. + /// + /// TODO: This should be called `PeerInfoRequest` instead. However, renaming it + /// will introduce a breaking change in the network and is not worth it. Do this + /// renaming when there is already a breaking change in the release. + NetworkInfo(network_info::NetworkInfoRequest), +} diff --git a/mm2src/mm2_p2p/src/application/request_response/network_info.rs b/mm2src/mm2_p2p/src/application/request_response/network_info.rs new file mode 100644 index 0000000000..c8dece2ef5 --- /dev/null +++ b/mm2src/mm2_p2p/src/application/request_response/network_info.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +/// Wraps the different types of network information requests for the P2P request-response +/// protocol. +#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum NetworkInfoRequest { + /// Get MM2 version of nodes added to stats collection + GetMm2Version, +} diff --git a/mm2src/mm2_p2p/src/application/request_response/ordermatch.rs b/mm2src/mm2_p2p/src/application/request_response/ordermatch.rs new file mode 100644 index 0000000000..250758f594 --- /dev/null +++ b/mm2src/mm2_p2p/src/application/request_response/ordermatch.rs @@ -0,0 +1,46 @@ +use mm2_number::BigRational; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +type AlbOrderedOrderbookPair = String; +type H64 = [u8; 8]; + +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum BestOrdersAction { + Buy, + Sell, +} + +/// Wraps the different types of order matching requests for the P2P request-response protocol. +/// +/// TODO: We should use fixed sizes for dynamic fields (such as strings and maps) +/// and prefer stricter types instead of accepting `String` for nearly everything. +/// See https://github.com/KomodoPlatform/komodo-defi-framework/issues/2236 for reference. +#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum OrdermatchRequest { + /// Get an orderbook for the given pair. + GetOrderbook { base: String, rel: String }, + /// Sync specific pubkey orderbook state if our known Patricia trie state doesn't match the latest keep alive message + SyncPubkeyOrderbookState { + pubkey: String, + /// Request using this condition + trie_roots: HashMap, + }, + /// Request best orders for a specific coin and action. + BestOrders { + coin: String, + action: BestOrdersAction, + volume: BigRational, + }, + /// Get orderbook depth for the specified pairs + OrderbookDepth { pairs: Vec<(String, String)> }, + /// Request best orders for a specific coin and action limited by the number of results. + /// + /// Q: Shouldn't we support pagination here? + BestOrdersByNumber { + coin: String, + action: BestOrdersAction, + number: usize, + }, +} diff --git a/mm2src/mm2_p2p/src/behaviours/atomicdex.rs b/mm2src/mm2_p2p/src/behaviours/atomicdex.rs index c1bcea5476..9d58da4e1e 100644 --- a/mm2src/mm2_p2p/src/behaviours/atomicdex.rs +++ b/mm2src/mm2_p2p/src/behaviours/atomicdex.rs @@ -18,7 +18,6 @@ use libp2p::{identity, noise, PeerId, Swarm}; use libp2p::{Multiaddr, Transport}; use log::{debug, error, info}; use rand::seq::SliceRandom; -use rand::Rng; use std::collections::hash_map::DefaultHasher; use std::collections::HashMap; use std::hash::{Hash, Hasher}; @@ -163,8 +162,8 @@ pub enum AdexBehaviourCmd { }, } -/// Returns info about connected peers -pub async fn get_peers_info(mut cmd_tx: AdexCmdTx) -> HashMap> { +/// Returns info about directly connected peers. +pub async fn get_directly_connected_peers(mut cmd_tx: AdexCmdTx) -> HashMap> { let (result_tx, rx) = oneshot::channel(); let cmd = AdexBehaviourCmd::GetPeersInfo { result_tx }; cmd_tx.send(cmd).await.expect("Rx should be present"); @@ -563,18 +562,10 @@ impl From for AdexBehaviourError { fn from(e: PublishError) -> Self { AdexBehaviourError::PublishError(e) } } -fn generate_ed25519_keypair(rng: &mut R, force_key: Option<[u8; 32]>) -> identity::Keypair { - let mut raw_key = match force_key { - Some(key) => key, - None => { - let mut key = [0; 32]; - rng.fill_bytes(&mut key); - key - }, - }; - let secret = identity::ed25519::SecretKey::try_from_bytes(&mut raw_key).expect("Secret length is 32 bytes"); +pub fn generate_ed25519_keypair(mut p2p_key: [u8; 32]) -> identity::Keypair { + let secret = identity::ed25519::SecretKey::try_from_bytes(&mut p2p_key).expect("Secret length is 32 bytes"); let keypair = identity::ed25519::Keypair::from(secret); - keypair.into() + identity::Keypair::from(keypair) } /// Custom types mapping the complex associated types of AtomicDexBehaviour to the ExpandedSwarm @@ -596,7 +587,7 @@ fn start_gossipsub( ) -> Result<(Sender, AdexEventRx, PeerId), AdexBehaviourError> { let i_am_relay = config.node_type.is_relay(); let mut rng = rand::thread_rng(); - let local_key = generate_ed25519_keypair(&mut rng, config.force_key); + let local_key = generate_ed25519_keypair(config.p2p_key); let local_peer_id = PeerId::from(local_key.public()); info!("Local peer id: {:?}", local_peer_id); @@ -1081,7 +1072,7 @@ impl NetworkBehaviour for AtomicDexBehaviour { pub struct GossipsubConfig { netid: u16, - force_key: Option<[u8; 32]>, + p2p_key: [u8; 32], runtime: SwarmRuntime, to_dial: Vec, node_type: NodeType, @@ -1091,9 +1082,12 @@ pub struct GossipsubConfig { impl GossipsubConfig { #[cfg(test)] pub(crate) fn new_for_tests(runtime: SwarmRuntime, to_dial: Vec, node_type: NodeType) -> Self { + let mut p2p_key = [0u8; 32]; + common::os_rng(&mut p2p_key).unwrap(); + GossipsubConfig { netid: 333, - force_key: None, + p2p_key, runtime, to_dial, node_type, @@ -1101,10 +1095,10 @@ impl GossipsubConfig { } } - pub fn new(netid: u16, runtime: SwarmRuntime, node_type: NodeType) -> Self { + pub fn new(netid: u16, runtime: SwarmRuntime, node_type: NodeType, p2p_key: [u8; 32]) -> Self { GossipsubConfig { netid, - force_key: None, + p2p_key, runtime, to_dial: vec![], node_type, @@ -1117,11 +1111,6 @@ impl GossipsubConfig { self } - pub fn force_key(&mut self, force_key: Option<[u8; 32]>) -> &mut Self { - self.force_key = force_key; - self - } - pub fn max_num_streams(&mut self, max_num_streams: usize) -> &mut Self { self.max_num_streams = max_num_streams; self diff --git a/mm2src/mm2_p2p/src/lib.rs b/mm2src/mm2_p2p/src/lib.rs index 03d6a301db..b1d0283be0 100644 --- a/mm2src/mm2_p2p/src/lib.rs +++ b/mm2src/mm2_p2p/src/lib.rs @@ -6,20 +6,26 @@ mod network; mod relay_address; mod swarm_runtime; +#[cfg(feature = "application")] pub mod application; +pub mod p2p_ctx; + +use derive_more::Display; use lazy_static::lazy_static; use secp256k1::{Message as SecpMessage, PublicKey as Secp256k1Pubkey, Secp256k1, SecretKey, SignOnly, Signature, VerifyOnly}; use serde::{de, Deserialize, Serialize, Serializer}; use sha2::digest::Update; use sha2::{Digest, Sha256}; +use std::str::FromStr; pub use crate::swarm_runtime::SwarmRuntime; // atomicdex related re-exports -pub use behaviours::atomicdex::{get_gossip_mesh, get_gossip_peer_topics, get_gossip_topic_peers, get_peers_info, - get_relay_mesh, spawn_gossipsub, AdexBehaviourCmd, AdexBehaviourError, - AdexBehaviourEvent, AdexCmdTx, AdexEventRx, AdexResponse, AdexResponseChannel, - GossipsubEvent, GossipsubMessage, MessageId, NodeType, TopicHash, WssCerts}; +pub use behaviours::atomicdex::{get_directly_connected_peers, get_gossip_mesh, get_gossip_peer_topics, + get_gossip_topic_peers, get_relay_mesh, spawn_gossipsub, AdexBehaviourCmd, + AdexBehaviourError, AdexBehaviourEvent, AdexCmdTx, AdexEventRx, AdexResponse, + AdexResponseChannel, GossipsubEvent, GossipsubMessage, MessageId, NodeType, TopicHash, + WssCerts}; // peers-exchange re-exports pub use behaviours::peers_exchange::PeerAddresses; @@ -29,7 +35,7 @@ pub use behaviours::request_response::RequestResponseBehaviourEvent; // libp2p related re-exports pub use libp2p::identity::DecodingError; -pub use libp2p::identity::{secp256k1::PublicKey as Libp2pSecpPublic, PublicKey as Libp2pPublic}; +pub use libp2p::identity::{secp256k1::PublicKey as Libp2pSecpPublic, Keypair, PublicKey as Libp2pPublic, SigningError}; pub use libp2p::{Multiaddr, PeerId}; // relay-address related re-exports @@ -42,6 +48,69 @@ lazy_static! { static ref SECP_SIGN: Secp256k1 = Secp256k1::signing_only(); } +/// Wrapper of `libp2p::PeerId` with trait additional implementations. +/// +/// TODO: This should be used as a replacement of `libp2p::PeerId` in the entire project. +#[derive(Clone, Copy, Debug, Display, Eq, Hash, PartialEq)] +pub struct PeerAddress(PeerId); + +impl From for PeerAddress { + fn from(value: PeerId) -> Self { Self(value) } +} + +impl From for PeerId { + fn from(value: PeerAddress) -> Self { value.0 } +} + +impl Serialize for PeerAddress { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.0.to_string()) + } +} + +impl<'de> Deserialize<'de> for PeerAddress { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct PeerAddressVisitor; + + impl<'de> serde::de::Visitor<'de> for PeerAddressVisitor { + type Value = PeerAddress; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string representation of peer id.") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + if value.len() > 100 { + return Err(serde::de::Error::invalid_length( + value.len(), + &"peer id cannot exceed 100 characters.", + )); + } + + Ok(PeerId::from_str(value).map_err(de::Error::custom)?.into()) + } + + fn visit_string(self, value: String) -> Result + where + E: de::Error, + { + self.visit_str(&value) + } + } + + deserializer.deserialize_str(PeerAddressVisitor) + } +} + #[derive(Clone, Copy, Debug)] pub enum NetworkInfo { /// The in-memory network. diff --git a/mm2src/mm2_net/src/p2p.rs b/mm2src/mm2_p2p/src/p2p_ctx.rs similarity index 62% rename from mm2src/mm2_net/src/p2p.rs rename to mm2src/mm2_p2p/src/p2p_ctx.rs index d76cd550bd..2d9d991298 100644 --- a/mm2src/mm2_net/src/p2p.rs +++ b/mm2src/mm2_p2p/src/p2p_ctx.rs @@ -1,20 +1,31 @@ +use libp2p::{identity::Keypair, PeerId}; use mm2_core::mm_ctx::MmArc; -use mm2_libp2p::behaviours::atomicdex::AdexCmdTx; use parking_lot::Mutex; use std::sync::Arc; +use crate::AdexCmdTx; + pub struct P2PContext { /// Using Mutex helps to prevent cloning which can actually result to channel being unbounded in case of using 1 tx clone per 1 message. pub cmd_tx: Mutex, + /// Host's keypair used for address derivation of peer and message signing. + keypair: Keypair, } impl P2PContext { - pub fn new(cmd_tx: AdexCmdTx) -> Self { + pub fn new(cmd_tx: AdexCmdTx, keypair: Keypair) -> Self { P2PContext { cmd_tx: Mutex::new(cmd_tx), + keypair, } } + #[inline(always)] + pub fn keypair(&self) -> &Keypair { &self.keypair } + + #[inline(always)] + pub fn peer_id(&self) -> PeerId { self.keypair.public().to_peer_id() } + pub fn store_to_mm_arc(self, ctx: &MmArc) { *ctx.p2p_ctx.lock().unwrap() = Some(Arc::new(self)) } pub fn fetch_from_mm_arc(ctx: &MmArc) -> Arc { diff --git a/mm2src/mm2_rpc/src/data/legacy/activation/utxo.rs b/mm2src/mm2_rpc/src/data/legacy/activation/utxo.rs index ff7c685bd2..d585424c57 100644 --- a/mm2src/mm2_rpc/src/data/legacy/activation/utxo.rs +++ b/mm2src/mm2_rpc/src/data/legacy/activation/utxo.rs @@ -13,8 +13,10 @@ pub struct UtxoMergeParams { #[allow(clippy::upper_case_acronyms)] #[derive(Clone, Debug, Deserialize, Serialize)] /// Deserializable Electrum protocol representation for RPC +#[derive(Default)] pub enum ElectrumProtocol { /// TCP + #[default] TCP, /// SSL/TLS SSL, @@ -25,10 +27,6 @@ pub enum ElectrumProtocol { } #[cfg(not(target_arch = "wasm32"))] -impl Default for ElectrumProtocol { - fn default() -> Self { ElectrumProtocol::TCP } -} - #[cfg(target_arch = "wasm32")] impl Default for ElectrumProtocol { fn default() -> Self { ElectrumProtocol::WS } diff --git a/mm2src/mm2_rpc/src/data/legacy/orders.rs b/mm2src/mm2_rpc/src/data/legacy/orders.rs index 8f50966da9..9890b1d709 100644 --- a/mm2src/mm2_rpc/src/data/legacy/orders.rs +++ b/mm2src/mm2_rpc/src/data/legacy/orders.rs @@ -74,27 +74,23 @@ pub enum TakerAction { #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", content = "data")] +#[derive(Default)] pub enum OrderType { FillOrKill, + #[default] GoodTillCancelled, } -impl Default for OrderType { - fn default() -> Self { OrderType::GoodTillCancelled } -} - #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", content = "data")] +#[derive(Default)] pub enum MatchBy { + #[default] Any, Orders(HashSet), Pubkeys(HashSet), } -impl Default for MatchBy { - fn default() -> Self { MatchBy::Any } -} - #[derive(Serialize, Deserialize)] pub struct OrderbookRequest { pub base: String, diff --git a/mm2src/mm2_test_helpers/contract_bytes/erc1155_test_token_bytes b/mm2src/mm2_test_helpers/contract_bytes/erc1155_test_token_bytes new file mode 100644 index 0000000000..d5610ec91b --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/erc1155_test_token_bytes @@ -0,0 +1 @@ +608060405234801562000010575f80fd5b50604051620024eb380380620024eb8339818101604052810190620000369190620001ea565b8062000048816200005060201b60201c565b505062000554565b806002908162000061919062000470565b5050565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b620000c6826200007e565b810181811067ffffffffffffffff82111715620000e857620000e76200008e565b5b80604052505050565b5f620000fc62000065565b90506200010a8282620000bb565b919050565b5f67ffffffffffffffff8211156200012c576200012b6200008e565b5b62000137826200007e565b9050602081019050919050565b5f5b838110156200016357808201518184015260208101905062000146565b5f8484015250505050565b5f620001846200017e846200010f565b620000f1565b905082815260208101848484011115620001a357620001a26200007a565b5b620001b084828562000144565b509392505050565b5f82601f830112620001cf57620001ce62000076565b5b8151620001e18482602086016200016e565b91505092915050565b5f602082840312156200020257620002016200006e565b5b5f82015167ffffffffffffffff81111562000222576200022162000072565b5b6200023084828501620001b8565b91505092915050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200028857607f821691505b6020821081036200029e576200029d62000243565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620003027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002c5565b6200030e8683620002c5565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f62000358620003526200034c8462000326565b6200032f565b62000326565b9050919050565b5f819050919050565b620003738362000338565b6200038b62000382826200035f565b848454620002d1565b825550505050565b5f90565b620003a162000393565b620003ae81848462000368565b505050565b5b81811015620003d557620003c95f8262000397565b600181019050620003b4565b5050565b601f8211156200042457620003ee81620002a4565b620003f984620002b6565b8101602085101562000409578190505b620004216200041885620002b6565b830182620003b3565b50505b505050565b5f82821c905092915050565b5f620004465f198460080262000429565b1980831691505092915050565b5f62000460838362000435565b9150826002028217905092915050565b6200047b8262000239565b67ffffffffffffffff8111156200049757620004966200008e565b5b620004a3825462000270565b620004b0828285620003d9565b5f60209050601f831160018114620004e6575f8415620004d1578287015190505b620004dd858262000453565b8655506200054c565b601f198416620004f686620002a4565b5f5b828110156200051f57848901518255600182019150602085019450602081019050620004f8565b868310156200053f57848901516200053b601f89168262000435565b8355505b6001600288020188555050505b505050505050565b611f8980620005625f395ff3fe608060405234801561000f575f80fd5b5060043610610090575f3560e01c80634e1273f4116100645780634e1273f414610140578063731133e914610170578063a22cb4651461018c578063e985e9c5146101a8578063f242432a146101d857610090565b8062fdd58e1461009457806301ffc9a7146100c45780630e89341c146100f45780632eb2c2d614610124575b5f80fd5b6100ae60048036038101906100a991906113bd565b6101f4565b6040516100bb919061140a565b60405180910390f35b6100de60048036038101906100d99190611478565b610249565b6040516100eb91906114bd565b60405180910390f35b61010e600480360381019061010991906114d6565b61032a565b60405161011b919061158b565b60405180910390f35b61013e6004803603810190610139919061179b565b6103bc565b005b61015a60048036038101906101559190611926565b610463565b6040516101679190611a53565b60405180910390f35b61018a60048036038101906101859190611a73565b61056a565b005b6101a660048036038101906101a19190611b1d565b61057c565b005b6101c260048036038101906101bd9190611b5b565b610592565b6040516101cf91906114bd565b60405180910390f35b6101f260048036038101906101ed9190611b99565b610620565b005b5f805f8381526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f7fd9b67a26000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061031357507f0e89341c000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103235750610322826106c7565b5b9050919050565b60606002805461033990611c59565b80601f016020809104026020016040519081016040528092919081815260200182805461036590611c59565b80156103b05780601f10610387576101008083540402835291602001916103b0565b820191905f5260205f20905b81548152906001019060200180831161039357829003601f168201915b50505050509050919050565b5f6103c5610730565b90508073ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161415801561040a57506104088682610592565b155b1561044e5780866040517fe237d922000000000000000000000000000000000000000000000000000000008152600401610445929190611c98565b60405180910390fd5b61045b8686868686610737565b505050505050565b606081518351146104af57815183516040517f5b0599910000000000000000000000000000000000000000000000000000000081526004016104a6929190611cbf565b60405180910390fd5b5f835167ffffffffffffffff8111156104cb576104ca6115af565b5b6040519080825280602002602001820160405280156104f95781602001602082028036833780820191505090505b5090505f5b845181101561055f5761053561051d828761082b90919063ffffffff16565b610530838761083e90919063ffffffff16565b6101f4565b82828151811061054857610547611ce6565b5b6020026020010181815250508060010190506104fe565b508091505092915050565b61057684848484610851565b50505050565b61058e610587610730565b83836108e6565b5050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f610629610730565b90508073ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161415801561066e575061066c8682610592565b155b156106b25780866040517fe237d9220000000000000000000000000000000000000000000000000000000081526004016106a9929190611c98565b60405180910390fd5b6106bf8686868686610a4f565b505050505050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f33905090565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036107a7575f6040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161079e9190611d13565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603610817575f6040517f01a8351400000000000000000000000000000000000000000000000000000000815260040161080e9190611d13565b60405180910390fd5b6108248585858585610b55565b5050505050565b5f60208202602084010151905092915050565b5f60208202602084010151905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036108c1575f6040517f57f447ce0000000000000000000000000000000000000000000000000000000081526004016108b89190611d13565b60405180910390fd5b5f806108cd8585610c01565b915091506108de5f87848487610b55565b505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610956575f6040517fced3e10000000000000000000000000000000000000000000000000000000000815260040161094d9190611d13565b60405180910390fd5b8060015f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610a4291906114bd565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610abf575f6040517f57f447ce000000000000000000000000000000000000000000000000000000008152600401610ab69190611d13565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603610b2f575f6040517f01a83514000000000000000000000000000000000000000000000000000000008152600401610b269190611d13565b60405180910390fd5b5f80610b3b8585610c01565b91509150610b4c8787848487610b55565b50505050505050565b610b6185858585610c31565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614610bfa575f610b9d610730565b90506001845103610be9575f610bbc5f8661083e90919063ffffffff16565b90505f610bd25f8661083e90919063ffffffff16565b9050610be2838989858589610fc1565b5050610bf8565b610bf7818787878787611170565b5b505b5050505050565b60608060405191506001825283602083015260408201905060018152826020820152604081016040529250929050565b8051825114610c7b57815181516040517f5b059991000000000000000000000000000000000000000000000000000000008152600401610c72929190611cbf565b60405180910390fd5b5f610c84610730565b90505f5b8351811015610e80575f610ca5828661083e90919063ffffffff16565b90505f610cbb838661083e90919063ffffffff16565b90505f73ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614610dde575f805f8481526020019081526020015f205f8a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905081811015610d8a57888183856040517f03dee4c5000000000000000000000000000000000000000000000000000000008152600401610d819493929190611d2c565b60405180910390fd5b8181035f808581526020019081526020015f205f8b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1614610e7357805f808481526020019081526020015f205f8973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f828254610e6b9190611d9c565b925050819055505b5050806001019050610c88565b506001835103610f3b575f610e9e5f8561083e90919063ffffffff16565b90505f610eb45f8561083e90919063ffffffff16565b90508573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f628585604051610f2c929190611cbf565b60405180910390a45050610fba565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8686604051610fb1929190611dcf565b60405180910390a45b5050505050565b5f8473ffffffffffffffffffffffffffffffffffffffff163b1115611168578373ffffffffffffffffffffffffffffffffffffffff1663f23a6e6187878686866040518663ffffffff1660e01b8152600401611021959493929190611e56565b6020604051808303815f875af192505050801561105c57506040513d601f19601f820116820180604052508101906110599190611ec2565b60015b6110dd573d805f811461108a576040519150601f19603f3d011682016040523d82523d5f602084013e61108f565b606091505b505f8151036110d557846040517f57f447ce0000000000000000000000000000000000000000000000000000000081526004016110cc9190611d13565b60405180910390fd5b805181602001fd5b63f23a6e6160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461116657846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161115d9190611d13565b60405180910390fd5b505b505050505050565b5f8473ffffffffffffffffffffffffffffffffffffffff163b1115611317578373ffffffffffffffffffffffffffffffffffffffff1663bc197c8187878686866040518663ffffffff1660e01b81526004016111d0959493929190611eed565b6020604051808303815f875af192505050801561120b57506040513d601f19601f820116820180604052508101906112089190611ec2565b60015b61128c573d805f8114611239576040519150601f19603f3d011682016040523d82523d5f602084013e61123e565b606091505b505f81510361128457846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161127b9190611d13565b60405180910390fd5b805181602001fd5b63bc197c8160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461131557846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161130c9190611d13565b60405180910390fd5b505b505050505050565b5f604051905090565b5f80fd5b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61135982611330565b9050919050565b6113698161134f565b8114611373575f80fd5b50565b5f8135905061138481611360565b92915050565b5f819050919050565b61139c8161138a565b81146113a6575f80fd5b50565b5f813590506113b781611393565b92915050565b5f80604083850312156113d3576113d2611328565b5b5f6113e085828601611376565b92505060206113f1858286016113a9565b9150509250929050565b6114048161138a565b82525050565b5f60208201905061141d5f8301846113fb565b92915050565b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61145781611423565b8114611461575f80fd5b50565b5f813590506114728161144e565b92915050565b5f6020828403121561148d5761148c611328565b5b5f61149a84828501611464565b91505092915050565b5f8115159050919050565b6114b7816114a3565b82525050565b5f6020820190506114d05f8301846114ae565b92915050565b5f602082840312156114eb576114ea611328565b5b5f6114f8848285016113a9565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b8381101561153857808201518184015260208101905061151d565b5f8484015250505050565b5f601f19601f8301169050919050565b5f61155d82611501565b611567818561150b565b935061157781856020860161151b565b61158081611543565b840191505092915050565b5f6020820190508181035f8301526115a38184611553565b905092915050565b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6115e582611543565b810181811067ffffffffffffffff82111715611604576116036115af565b5b80604052505050565b5f61161661131f565b905061162282826115dc565b919050565b5f67ffffffffffffffff821115611641576116406115af565b5b602082029050602081019050919050565b5f80fd5b5f61166861166384611627565b61160d565b9050808382526020820190506020840283018581111561168b5761168a611652565b5b835b818110156116b457806116a088826113a9565b84526020840193505060208101905061168d565b5050509392505050565b5f82601f8301126116d2576116d16115ab565b5b81356116e2848260208601611656565b91505092915050565b5f80fd5b5f67ffffffffffffffff821115611709576117086115af565b5b61171282611543565b9050602081019050919050565b828183375f83830152505050565b5f61173f61173a846116ef565b61160d565b90508281526020810184848401111561175b5761175a6116eb565b5b61176684828561171f565b509392505050565b5f82601f830112611782576117816115ab565b5b813561179284826020860161172d565b91505092915050565b5f805f805f60a086880312156117b4576117b3611328565b5b5f6117c188828901611376565b95505060206117d288828901611376565b945050604086013567ffffffffffffffff8111156117f3576117f261132c565b5b6117ff888289016116be565b935050606086013567ffffffffffffffff8111156118205761181f61132c565b5b61182c888289016116be565b925050608086013567ffffffffffffffff81111561184d5761184c61132c565b5b6118598882890161176e565b9150509295509295909350565b5f67ffffffffffffffff8211156118805761187f6115af565b5b602082029050602081019050919050565b5f6118a361189e84611866565b61160d565b905080838252602082019050602084028301858111156118c6576118c5611652565b5b835b818110156118ef57806118db8882611376565b8452602084019350506020810190506118c8565b5050509392505050565b5f82601f83011261190d5761190c6115ab565b5b813561191d848260208601611891565b91505092915050565b5f806040838503121561193c5761193b611328565b5b5f83013567ffffffffffffffff8111156119595761195861132c565b5b611965858286016118f9565b925050602083013567ffffffffffffffff8111156119865761198561132c565b5b611992858286016116be565b9150509250929050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b6119ce8161138a565b82525050565b5f6119df83836119c5565b60208301905092915050565b5f602082019050919050565b5f611a018261199c565b611a0b81856119a6565b9350611a16836119b6565b805f5b83811015611a46578151611a2d88826119d4565b9750611a38836119eb565b925050600181019050611a19565b5085935050505092915050565b5f6020820190508181035f830152611a6b81846119f7565b905092915050565b5f805f8060808587031215611a8b57611a8a611328565b5b5f611a9887828801611376565b9450506020611aa9878288016113a9565b9350506040611aba878288016113a9565b925050606085013567ffffffffffffffff811115611adb57611ada61132c565b5b611ae78782880161176e565b91505092959194509250565b611afc816114a3565b8114611b06575f80fd5b50565b5f81359050611b1781611af3565b92915050565b5f8060408385031215611b3357611b32611328565b5b5f611b4085828601611376565b9250506020611b5185828601611b09565b9150509250929050565b5f8060408385031215611b7157611b70611328565b5b5f611b7e85828601611376565b9250506020611b8f85828601611376565b9150509250929050565b5f805f805f60a08688031215611bb257611bb1611328565b5b5f611bbf88828901611376565b9550506020611bd088828901611376565b9450506040611be1888289016113a9565b9350506060611bf2888289016113a9565b925050608086013567ffffffffffffffff811115611c1357611c1261132c565b5b611c1f8882890161176e565b9150509295509295909350565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680611c7057607f821691505b602082108103611c8357611c82611c2c565b5b50919050565b611c928161134f565b82525050565b5f604082019050611cab5f830185611c89565b611cb86020830184611c89565b9392505050565b5f604082019050611cd25f8301856113fb565b611cdf60208301846113fb565b9392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f602082019050611d265f830184611c89565b92915050565b5f608082019050611d3f5f830187611c89565b611d4c60208301866113fb565b611d5960408301856113fb565b611d6660608301846113fb565b95945050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f611da68261138a565b9150611db18361138a565b9250828201905080821115611dc957611dc8611d6f565b5b92915050565b5f6040820190508181035f830152611de781856119f7565b90508181036020830152611dfb81846119f7565b90509392505050565b5f81519050919050565b5f82825260208201905092915050565b5f611e2882611e04565b611e328185611e0e565b9350611e4281856020860161151b565b611e4b81611543565b840191505092915050565b5f60a082019050611e695f830188611c89565b611e766020830187611c89565b611e8360408301866113fb565b611e9060608301856113fb565b8181036080830152611ea28184611e1e565b90509695505050505050565b5f81519050611ebc8161144e565b92915050565b5f60208284031215611ed757611ed6611328565b5b5f611ee484828501611eae565b91505092915050565b5f60a082019050611f005f830188611c89565b611f0d6020830187611c89565b8181036040830152611f1f81866119f7565b90508181036060830152611f3381856119f7565b90508181036080830152611f478184611e1e565b9050969550505050505056fea26469706673582212203835581c6344b12728c44fa4d9e912cd60e64012c1b772bb703d1c36825c16fd64736f6c63430008180033 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/contract_bytes/erc20_token_bytes b/mm2src/mm2_test_helpers/contract_bytes/erc20_token_bytes new file mode 100644 index 0000000000..21d608c62d --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/erc20_token_bytes @@ -0,0 +1 @@ +6080604052600860ff16600a0a633b9aca000260005534801561002157600080fd5b50600054600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610c69806100776000396000f3006080604052600436106100a4576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100a9578063095ea7b31461013957806318160ddd1461019e57806323b872dd146101c9578063313ce5671461024e5780635a3b7e421461027f57806370a082311461030f57806395d89b4114610366578063a9059cbb146103f6578063dd62ed3e1461045b575b600080fd5b3480156100b557600080fd5b506100be6104d2565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100fe5780820151818401526020810190506100e3565b50505050905090810190601f16801561012b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561014557600080fd5b50610184600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061050b565b604051808215151515815260200191505060405180910390f35b3480156101aa57600080fd5b506101b36106bb565b6040518082815260200191505060405180910390f35b3480156101d557600080fd5b50610234600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506106c1565b604051808215151515815260200191505060405180910390f35b34801561025a57600080fd5b506102636109a1565b604051808260ff1660ff16815260200191505060405180910390f35b34801561028b57600080fd5b506102946109a6565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156102d45780820151818401526020810190506102b9565b50505050905090810190601f1680156103015780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561031b57600080fd5b50610350600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506109df565b6040518082815260200191505060405180910390f35b34801561037257600080fd5b5061037b6109f7565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103bb5780820151818401526020810190506103a0565b50505050905090810190601f1680156103e85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561040257600080fd5b50610441600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610a30565b604051808215151515815260200191505060405180910390f35b34801561046757600080fd5b506104bc600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610be1565b6040518082815260200191505060405180910390f35b6040805190810160405280600881526020017f515243205445535400000000000000000000000000000000000000000000000081525081565b60008260008173ffffffffffffffffffffffffffffffffffffffff161415151561053457600080fd5b60008314806105bf57506000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054145b15156105ca57600080fd5b82600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925856040518082815260200191505060405180910390a3600191505092915050565b60005481565b60008360008173ffffffffffffffffffffffffffffffffffffffff16141515156106ea57600080fd5b8360008173ffffffffffffffffffffffffffffffffffffffff161415151561071157600080fd5b610797600260008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c06565b600260008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610860600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c06565b600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506108ec600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c1f565b600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef866040518082815260200191505060405180910390a36001925050509392505050565b600881565b6040805190810160405280600981526020017f546f6b656e20302e31000000000000000000000000000000000000000000000081525081565b60016020528060005260406000206000915090505481565b6040805190810160405280600381526020017f515443000000000000000000000000000000000000000000000000000000000081525081565b60008260008173ffffffffffffffffffffffffffffffffffffffff1614151515610a5957600080fd5b610aa2600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205484610c06565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610b2e600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205484610c1f565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a3600191505092915050565b6002602052816000526040600020602052806000526040600020600091509150505481565b6000818310151515610c1457fe5b818303905092915050565b6000808284019050838110151515610c3357fe5b80915050929150505600a165627a7a723058207f2e5248b61b80365ea08a0f6d11ac0b47374c4dfd538de76bc2f19591bbbba40029 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/contract_bytes/erc721_test_token_bytes b/mm2src/mm2_test_helpers/contract_bytes/erc721_test_token_bytes new file mode 100644 index 0000000000..62f7b588b2 --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/erc721_test_token_bytes @@ -0,0 +1 @@ +608060405234801562000010575f80fd5b50604051620022ac380380620022ac8339818101604052810190620000369190620001ea565b8181815f9081620000489190620004a4565b5080600190816200005a9190620004a4565b505050505062000588565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b620000c6826200007e565b810181811067ffffffffffffffff82111715620000e857620000e76200008e565b5b80604052505050565b5f620000fc62000065565b90506200010a8282620000bb565b919050565b5f67ffffffffffffffff8211156200012c576200012b6200008e565b5b62000137826200007e565b9050602081019050919050565b5f5b838110156200016357808201518184015260208101905062000146565b5f8484015250505050565b5f620001846200017e846200010f565b620000f1565b905082815260208101848484011115620001a357620001a26200007a565b5b620001b084828562000144565b509392505050565b5f82601f830112620001cf57620001ce62000076565b5b8151620001e18482602086016200016e565b91505092915050565b5f80604083850312156200020357620002026200006e565b5b5f83015167ffffffffffffffff81111562000223576200022262000072565b5b6200023185828601620001b8565b925050602083015167ffffffffffffffff81111562000255576200025462000072565b5b6200026385828601620001b8565b9150509250929050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680620002bc57607f821691505b602082108103620002d257620002d162000277565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620003367fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002f9565b620003428683620002f9565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f6200038c6200038662000380846200035a565b62000363565b6200035a565b9050919050565b5f819050919050565b620003a7836200036c565b620003bf620003b68262000393565b84845462000305565b825550505050565b5f90565b620003d5620003c7565b620003e28184846200039c565b505050565b5b818110156200040957620003fd5f82620003cb565b600181019050620003e8565b5050565b601f82111562000458576200042281620002d8565b6200042d84620002ea565b810160208510156200043d578190505b620004556200044c85620002ea565b830182620003e7565b50505b505050565b5f82821c905092915050565b5f6200047a5f19846008026200045d565b1980831691505092915050565b5f62000494838362000469565b9150826002028217905092915050565b620004af826200026d565b67ffffffffffffffff811115620004cb57620004ca6200008e565b5b620004d78254620002a4565b620004e48282856200040d565b5f60209050601f8311600181146200051a575f841562000505578287015190505b62000511858262000487565b86555062000580565b601f1984166200052a86620002d8565b5f5b8281101562000553578489015182556001820191506020850194506020810190506200052c565b868310156200057357848901516200056f601f89168262000469565b8355505b6001600288020188555050505b505050505050565b611d1680620005965f395ff3fe608060405234801561000f575f80fd5b50600436106100e8575f3560e01c80636352211e1161008a578063a22cb46511610064578063a22cb46514610258578063b88d4fde14610274578063c87b56dd14610290578063e985e9c5146102c0576100e8565b80636352211e146101da57806370a082311461020a57806395d89b411461023a576100e8565b8063095ea7b3116100c6578063095ea7b31461016a57806323b872dd1461018657806340c10f19146101a257806342842e0e146101be576100e8565b806301ffc9a7146100ec57806306fdde031461011c578063081812fc1461013a575b5f80fd5b610106600480360381019061010191906115a7565b6102f0565b60405161011391906115ec565b60405180910390f35b6101246103d1565b604051610131919061168f565b60405180910390f35b610154600480360381019061014f91906116e2565b610460565b604051610161919061174c565b60405180910390f35b610184600480360381019061017f919061178f565b61047b565b005b6101a0600480360381019061019b91906117cd565b610491565b005b6101bc60048036038101906101b7919061178f565b610590565b005b6101d860048036038101906101d391906117cd565b61059e565b005b6101f460048036038101906101ef91906116e2565b6105bd565b604051610201919061174c565b60405180910390f35b610224600480360381019061021f919061181d565b6105ce565b6040516102319190611857565b60405180910390f35b610242610684565b60405161024f919061168f565b60405180910390f35b610272600480360381019061026d919061189a565b610714565b005b61028e60048036038101906102899190611a04565b61072a565b005b6102aa60048036038101906102a591906116e2565b610747565b6040516102b7919061168f565b60405180910390f35b6102da60048036038101906102d59190611a84565b6107ad565b6040516102e791906115ec565b60405180910390f35b5f7f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806103ba57507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103ca57506103c98261083b565b5b9050919050565b60605f80546103df90611aef565b80601f016020809104026020016040519081016040528092919081815260200182805461040b90611aef565b80156104565780601f1061042d57610100808354040283529160200191610456565b820191905f5260205f20905b81548152906001019060200180831161043957829003601f168201915b5050505050905090565b5f61046a826108a4565b506104748261092a565b9050919050565b61048d8282610488610963565b61096a565b5050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610501575f6040517f64a0ae920000000000000000000000000000000000000000000000000000000081526004016104f8919061174c565b60405180910390fd5b5f610514838361050f610963565b61097c565b90508373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161461058a578382826040517f64283d7b00000000000000000000000000000000000000000000000000000000815260040161058193929190611b1f565b60405180910390fd5b50505050565b61059a8282610b87565b5050565b6105b883838360405180602001604052805f81525061072a565b505050565b5f6105c7826108a4565b9050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361063f575f6040517f89c62b64000000000000000000000000000000000000000000000000000000008152600401610636919061174c565b60405180910390fd5b60035f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b60606001805461069390611aef565b80601f01602080910402602001604051908101604052809291908181526020018280546106bf90611aef565b801561070a5780601f106106e15761010080835404028352916020019161070a565b820191905f5260205f20905b8154815290600101906020018083116106ed57829003601f168201915b5050505050905090565b61072661071f610963565b8383610c7a565b5050565b610735848484610491565b61074184848484610de3565b50505050565b6060610752826108a4565b505f61075c610f95565b90505f81511161077a5760405180602001604052805f8152506107a5565b8061078484610fab565b604051602001610795929190611b8e565b6040516020818303038152906040525b915050919050565b5f60055f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f806108af83611075565b90505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092157826040517f7e2732890000000000000000000000000000000000000000000000000000000081526004016109189190611857565b60405180910390fd5b80915050919050565b5f60045f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b5f33905090565b61097783838360016110ae565b505050565b5f8061098784611075565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16146109c8576109c781848661126d565b5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610a5357610a075f855f806110ae565b600160035f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825403925050819055505b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614610ad257600160035f8773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8460025f8681526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550838573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4809150509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610bf7575f6040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610bee919061174c565b60405180910390fd5b5f610c0383835f61097c565b90505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610c75575f6040517f73c6ac6e000000000000000000000000000000000000000000000000000000008152600401610c6c919061174c565b60405180910390fd5b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610cea57816040517f5b08ba18000000000000000000000000000000000000000000000000000000008152600401610ce1919061174c565b60405180910390fd5b8060055f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610dd691906115ec565b60405180910390a3505050565b5f8373ffffffffffffffffffffffffffffffffffffffff163b1115610f8f578273ffffffffffffffffffffffffffffffffffffffff1663150b7a02610e26610963565b8685856040518563ffffffff1660e01b8152600401610e489493929190611c03565b6020604051808303815f875af1925050508015610e8357506040513d601f19601f82011682018060405250810190610e809190611c61565b60015b610f04573d805f8114610eb1576040519150601f19603f3d011682016040523d82523d5f602084013e610eb6565b606091505b505f815103610efc57836040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610ef3919061174c565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614610f8d57836040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610f84919061174c565b60405180910390fd5b505b50505050565b606060405180602001604052805f815250905090565b60605f6001610fb984611330565b0190505f8167ffffffffffffffff811115610fd757610fd66118e0565b5b6040519080825280601f01601f1916602001820160405280156110095781602001600182028036833780820191505090505b5090505f82602001820190505b60011561106a578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a858161105f5761105e611c8c565b5b0494505f8503611016575b819350505050919050565b5f60025f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b80806110e657505f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b15611218575f6110f5846108a4565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561115f57508273ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b8015611172575061117081846107ad565b155b156111b457826040517fa9fbf51f0000000000000000000000000000000000000000000000000000000081526004016111ab919061174c565b60405180910390fd5b811561121657838573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45b505b8360045f8581526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050565b611278838383611481565b61132b575f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036112ec57806040517f7e2732890000000000000000000000000000000000000000000000000000000081526004016112e39190611857565b60405180910390fd5b81816040517f177e802f000000000000000000000000000000000000000000000000000000008152600401611322929190611cb9565b60405180910390fd5b505050565b5f805f90507a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000831061138c577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161138257611381611c8c565b5b0492506040810190505b6d04ee2d6d415b85acef810000000083106113c9576d04ee2d6d415b85acef810000000083816113bf576113be611c8c565b5b0492506020810190505b662386f26fc1000083106113f857662386f26fc1000083816113ee576113ed611c8c565b5b0492506010810190505b6305f5e1008310611421576305f5e100838161141757611416611c8c565b5b0492506008810190505b612710831061144657612710838161143c5761143b611c8c565b5b0492506004810190505b60648310611469576064838161145f5761145e611c8c565b5b0492506002810190505b600a8310611478576001810190505b80915050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561153857508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614806114f957506114f884846107ad565b5b8061153757508273ffffffffffffffffffffffffffffffffffffffff1661151f8361092a565b73ffffffffffffffffffffffffffffffffffffffff16145b5b90509392505050565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61158681611552565b8114611590575f80fd5b50565b5f813590506115a18161157d565b92915050565b5f602082840312156115bc576115bb61154a565b5b5f6115c984828501611593565b91505092915050565b5f8115159050919050565b6115e6816115d2565b82525050565b5f6020820190506115ff5f8301846115dd565b92915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b8381101561163c578082015181840152602081019050611621565b5f8484015250505050565b5f601f19601f8301169050919050565b5f61166182611605565b61166b818561160f565b935061167b81856020860161161f565b61168481611647565b840191505092915050565b5f6020820190508181035f8301526116a78184611657565b905092915050565b5f819050919050565b6116c1816116af565b81146116cb575f80fd5b50565b5f813590506116dc816116b8565b92915050565b5f602082840312156116f7576116f661154a565b5b5f611704848285016116ce565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6117368261170d565b9050919050565b6117468161172c565b82525050565b5f60208201905061175f5f83018461173d565b92915050565b61176e8161172c565b8114611778575f80fd5b50565b5f8135905061178981611765565b92915050565b5f80604083850312156117a5576117a461154a565b5b5f6117b28582860161177b565b92505060206117c3858286016116ce565b9150509250929050565b5f805f606084860312156117e4576117e361154a565b5b5f6117f18682870161177b565b93505060206118028682870161177b565b9250506040611813868287016116ce565b9150509250925092565b5f602082840312156118325761183161154a565b5b5f61183f8482850161177b565b91505092915050565b611851816116af565b82525050565b5f60208201905061186a5f830184611848565b92915050565b611879816115d2565b8114611883575f80fd5b50565b5f8135905061189481611870565b92915050565b5f80604083850312156118b0576118af61154a565b5b5f6118bd8582860161177b565b92505060206118ce85828601611886565b9150509250929050565b5f80fd5b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61191682611647565b810181811067ffffffffffffffff82111715611935576119346118e0565b5b80604052505050565b5f611947611541565b9050611953828261190d565b919050565b5f67ffffffffffffffff821115611972576119716118e0565b5b61197b82611647565b9050602081019050919050565b828183375f83830152505050565b5f6119a86119a384611958565b61193e565b9050828152602081018484840111156119c4576119c36118dc565b5b6119cf848285611988565b509392505050565b5f82601f8301126119eb576119ea6118d8565b5b81356119fb848260208601611996565b91505092915050565b5f805f8060808587031215611a1c57611a1b61154a565b5b5f611a298782880161177b565b9450506020611a3a8782880161177b565b9350506040611a4b878288016116ce565b925050606085013567ffffffffffffffff811115611a6c57611a6b61154e565b5b611a78878288016119d7565b91505092959194509250565b5f8060408385031215611a9a57611a9961154a565b5b5f611aa78582860161177b565b9250506020611ab88582860161177b565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680611b0657607f821691505b602082108103611b1957611b18611ac2565b5b50919050565b5f606082019050611b325f83018661173d565b611b3f6020830185611848565b611b4c604083018461173d565b949350505050565b5f81905092915050565b5f611b6882611605565b611b728185611b54565b9350611b8281856020860161161f565b80840191505092915050565b5f611b998285611b5e565b9150611ba58284611b5e565b91508190509392505050565b5f81519050919050565b5f82825260208201905092915050565b5f611bd582611bb1565b611bdf8185611bbb565b9350611bef81856020860161161f565b611bf881611647565b840191505092915050565b5f608082019050611c165f83018761173d565b611c23602083018661173d565b611c306040830185611848565b8181036060830152611c428184611bcb565b905095945050505050565b5f81519050611c5b8161157d565b92915050565b5f60208284031215611c7657611c7561154a565b5b5f611c8384828501611c4d565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f604082019050611ccc5f83018561173d565b611cd96020830184611848565b939250505056fea26469706673582212207439b47c2a9a1624955997732075917bbf1da26949d000c778f561eb5687576164736f6c63430008180033 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/contract_bytes/maker_swap_v2_bytes b/mm2src/mm2_test_helpers/contract_bytes/maker_swap_v2_bytes new file mode 100644 index 0000000000..5ac667f370 --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/maker_swap_v2_bytes @@ -0,0 +1 @@ +6080604052348015600e575f80fd5b50611d9f8061001c5f395ff3fe608060405260043610610054575f3560e01c80631299a27a146100585780637466be601461008057806374a4788a1461009c5780639b949dee146100c4578063a53bc126146100ec578063efccb9eb14610114575b5f80fd5b348015610063575f80fd5b5061007e60048036038101906100799190611427565b610152565b005b61009a600480360381019061009591906114e9565b61044b565b005b3480156100a7575f80fd5b506100c260048036038101906100bd9190611427565b6106fc565b005b3480156100cf575f80fd5b506100ea60048036038101906100e59190611427565b6109f5565b005b3480156100f7575f80fd5b50610112600480360381019061010d9190611560565b610ced565b005b34801561011f575f80fd5b5061013a600480360381019061013591906115fd565b610fd2565b604051610149939291906116e4565b60405180910390f35b6001600381111561016657610165611671565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561019857610197611671565b5b146101d8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101cf90611799565b60405180910390fd5b5f6003863387876002886040516020016101f291906117d7565b60405160208183030381529060405260405161020e9190611843565b602060405180830381855afa158015610229573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061024c919061186d565b87604051602001610262969594939291906118fd565b60405160208183030381529060405260405161027e9190611843565b602060405180830381855afa158015610299573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610323576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161031a906119b6565b60405180910390fd5b60025f808981526020019081526020015f205f0160186101000a81548160ff0219169083600381111561035957610358611671565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08760405161038d91906119e3565b60405180910390a15f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610411573373ffffffffffffffffffffffffffffffffffffffff166108fc8790811502906040515f60405180830381858888f1935050505015801561040b573d5f803e3d5ffd5b50610442565b5f82905061044033888373ffffffffffffffffffffffffffffffffffffffff1661101e9092919063ffffffff16565b505b50505050505050565b5f600381111561045e5761045d611671565b5b5f808781526020019081526020015f205f0160189054906101000a900460ff1660038111156104905761048f611671565b5b146104d0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104c790611a6c565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361053e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161053590611ad4565b60405180910390fd5b5f3411610580576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161057790611b62565b60405180910390fd5b5f600334863387875f60405160200161059e969594939291906118fd565b6040516020818303038152906040526040516105ba9190611843565b602060405180830381855afa1580156105d5573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018363ffffffff1681526020016001600381111561062157610620611671565b5b8152505f808881526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff021916908360038111156106b5576106b4611671565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad866040516106ec91906119e3565b60405180910390a1505050505050565b600160038111156107105761070f611671565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561074257610741611671565b5b14610782576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161077990611799565b60405180910390fd5b5f600386863360028860405160200161079b91906117d7565b6040516020818303038152906040526040516107b79190611843565b602060405180830381855afa1580156107d2573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906107f5919061186d565b878760405160200161080c969594939291906118fd565b6040516020818303038152906040526040516108289190611843565b602060405180830381855afa158015610843573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146108cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108c4906119b6565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff0219169083600381111561090357610902611671565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd738760405161093791906119e3565b60405180910390a15f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036109bb573373ffffffffffffffffffffffffffffffffffffffff166108fc8790811502906040515f60405180830381858888f193505050501580156109b5573d5f803e3d5ffd5b506109ec565b5f8290506109ea33888373ffffffffffffffffffffffffffffffffffffffff1661101e9092919063ffffffff16565b505b50505050505050565b60016003811115610a0957610a08611671565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff166003811115610a3b57610a3a611671565b5b14610a7b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a7290611799565b60405180910390fd5b5f6003868633878787604051602001610a99969594939291906118fd565b604051602081830303815290604052604051610ab59190611843565b602060405180830381855afa158015610ad0573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610b5a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b51906119b6565b60405180910390fd5b5f808881526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff16421015610bc5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610bbc90611bf0565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff02191690836003811115610bfb57610bfa611671565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad1921907287604051610c2f91906119e3565b60405180910390a15f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610cb3573373ffffffffffffffffffffffffffffffffffffffff166108fc8790811502906040515f60405180830381858888f19350505050158015610cad573d5f803e3d5ffd5b50610ce4565b5f829050610ce233888373ffffffffffffffffffffffffffffffffffffffff1661101e9092919063ffffffff16565b505b50505050505050565b5f6003811115610d0057610cff611671565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115610d3257610d31611671565b5b14610d72576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d6990611a6c565b60405180910390fd5b5f8611610db4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dab90611c58565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610e22576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e1990611ad4565b60405180910390fd5b5f600387863387878b604051602001610e40969594939291906118fd565b604051602081830303815290604052604051610e5c9190611843565b602060405180830381855afa158015610e77573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018363ffffffff16815260200160016003811115610ec357610ec2611671565b5b8152505f808a81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff02191690836003811115610f5757610f56611671565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad88604051610f8e91906119e3565b60405180910390a15f869050610fc733308a8473ffffffffffffffffffffffffffffffffffffffff1661109d909392919063ffffffff16565b505050505050505050565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900463ffffffff1690805f0160189054906101000a900460ff16905083565b611098838473ffffffffffffffffffffffffffffffffffffffff1663a9059cbb8585604051602401611051929190611c94565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505061111f565b505050565b611119848573ffffffffffffffffffffffffffffffffffffffff166323b872dd8686866040516024016110d293929190611cbb565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505061111f565b50505050565b5f611149828473ffffffffffffffffffffffffffffffffffffffff166111b490919063ffffffff16565b90505f81511415801561116d57508080602001905181019061116b9190611d25565b155b156111af57826040517f5274afe70000000000000000000000000000000000000000000000000000000081526004016111a69190611d50565b60405180910390fd5b505050565b60606111c183835f6111c9565b905092915050565b60608147101561121057306040517fcd7860590000000000000000000000000000000000000000000000000000000081526004016112079190611d50565b60405180910390fd5b5f808573ffffffffffffffffffffffffffffffffffffffff1684866040516112389190611843565b5f6040518083038185875af1925050503d805f8114611272576040519150601f19603f3d011682016040523d82523d5f602084013e611277565b606091505b5091509150611287868383611292565b925050509392505050565b6060826112a7576112a28261131f565b611317565b5f82511480156112cd57505f8473ffffffffffffffffffffffffffffffffffffffff163b145b1561130f57836040517f9996b3150000000000000000000000000000000000000000000000000000000081526004016113069190611d50565b60405180910390fd5b819050611318565b5b9392505050565b5f815111156113315780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f80fd5b5f819050919050565b61137981611367565b8114611383575f80fd5b50565b5f8135905061139481611370565b92915050565b5f819050919050565b6113ac8161139a565b81146113b6575f80fd5b50565b5f813590506113c7816113a3565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6113f6826113cd565b9050919050565b611406816113ec565b8114611410575f80fd5b50565b5f81359050611421816113fd565b92915050565b5f805f805f8060c0878903121561144157611440611363565b5b5f61144e89828a01611386565b965050602061145f89828a016113b9565b955050604061147089828a01611413565b945050606061148189828a01611386565b935050608061149289828a01611386565b92505060a06114a389828a01611413565b9150509295509295509295565b5f63ffffffff82169050919050565b6114c8816114b0565b81146114d2575f80fd5b50565b5f813590506114e3816114bf565b92915050565b5f805f805f60a0868803121561150257611501611363565b5b5f61150f88828901611386565b955050602061152088828901611413565b945050604061153188828901611386565b935050606061154288828901611386565b9250506080611553888289016114d5565b9150509295509295909350565b5f805f805f805f60e0888a03121561157b5761157a611363565b5b5f6115888a828b01611386565b97505060206115998a828b016113b9565b96505060406115aa8a828b01611413565b95505060606115bb8a828b01611413565b94505060806115cc8a828b01611386565b93505060a06115dd8a828b01611386565b92505060c06115ee8a828b016114d5565b91505092959891949750929550565b5f6020828403121561161257611611611363565b5b5f61161f84828501611386565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b61165c81611628565b82525050565b61166b816114b0565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600481106116af576116ae611671565b5b50565b5f8190506116bf8261169e565b919050565b5f6116ce826116b2565b9050919050565b6116de816116c4565b82525050565b5f6060820190506116f75f830186611653565b6117046020830185611662565b61171160408301846116d5565b949350505050565b5f82825260208201905092915050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520505f8201527f61796d656e7453656e7400000000000000000000000000000000000000000000602082015250565b5f611783602a83611719565b915061178e82611729565b604082019050919050565b5f6020820190508181035f8301526117b081611777565b9050919050565b5f819050919050565b6117d16117cc82611367565b6117b7565b82525050565b5f6117e282846117c0565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f61181d826117f1565b61182781856117fb565b9350611837818560208601611805565b80840191505092915050565b5f61184e8284611813565b915081905092915050565b5f8151905061186781611370565b92915050565b5f6020828403121561188257611881611363565b5b5f61188f84828501611859565b91505092915050565b5f819050919050565b6118b26118ad8261139a565b611898565b82525050565b5f8160601b9050919050565b5f6118ce826118b8565b9050919050565b5f6118df826118c4565b9050919050565b6118f76118f2826113ec565b6118d5565b82525050565b5f61190882896118a1565b60208201915061191882886118e6565b60148201915061192882876118e6565b60148201915061193882866117c0565b60208201915061194882856117c0565b60208201915061195882846118e6565b601482019150819050979650505050505050565b7f496e76616c6964207061796d656e7448617368000000000000000000000000005f82015250565b5f6119a0601383611719565b91506119ab8261196c565b602082019050919050565b5f6020820190508181035f8301526119cd81611994565b9050919050565b6119dd81611367565b82525050565b5f6020820190506119f65f8301846119d4565b92915050565b7f4d616b6572207061796d656e7420697320616c726561647920696e697469616c5f8201527f697a656400000000000000000000000000000000000000000000000000000000602082015250565b5f611a56602483611719565b9150611a61826119fc565b604082019050919050565b5f6020820190508181035f830152611a8381611a4a565b9050919050565b7f54616b6572206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f611abe601e83611719565b9150611ac982611a8a565b602082019050919050565b5f6020820190508181035f830152611aeb81611ab2565b9050919050565b7f4554482076616c7565206d7573742062652067726561746572207468616e207a5f8201527f65726f0000000000000000000000000000000000000000000000000000000000602082015250565b5f611b4c602383611719565b9150611b5782611af2565b604082019050919050565b5f6020820190508181035f830152611b7981611b40565b9050919050565b7f43757272656e742074696d657374616d70206469646e277420657863656564205f8201527f7061796d656e7420726566756e64206c6f636b2074696d650000000000000000602082015250565b5f611bda603883611719565b9150611be582611b80565b604082019050919050565b5f6020820190508181035f830152611c0781611bce565b9050919050565b7f416d6f756e74206d757374206e6f74206265207a65726f0000000000000000005f82015250565b5f611c42601783611719565b9150611c4d82611c0e565b602082019050919050565b5f6020820190508181035f830152611c6f81611c36565b9050919050565b611c7f816113ec565b82525050565b611c8e8161139a565b82525050565b5f604082019050611ca75f830185611c76565b611cb46020830184611c85565b9392505050565b5f606082019050611cce5f830186611c76565b611cdb6020830185611c76565b611ce86040830184611c85565b949350505050565b5f8115159050919050565b611d0481611cf0565b8114611d0e575f80fd5b50565b5f81519050611d1f81611cfb565b92915050565b5f60208284031215611d3a57611d39611363565b5b5f611d4784828501611d11565b91505092915050565b5f602082019050611d635f830184611c76565b9291505056fea26469706673582212200d7b67fe7b5e6829da1f56f14431b4ee61dc3a66fb17a79f0587fda0329d09ba64736f6c634300081a0033 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/contract_bytes/nft_maker_swap_v2_bytes b/mm2src/mm2_test_helpers/contract_bytes/nft_maker_swap_v2_bytes new file mode 100644 index 0000000000..ea4cd8c817 --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/nft_maker_swap_v2_bytes @@ -0,0 +1 @@ +6080604052348015600e575f80fd5b50612ffd8061001c5f395ff3fe608060405234801561000f575f80fd5b50600436106100a7575f3560e01c8063b27e46fb1161006f578063b27e46fb1461015f578063bc197c811461017b578063c8d9009b146101ab578063c92cd12d146101c7578063efccb9eb146101e3578063f23a6e6114610215576100a7565b806301ffc9a7146100ab57806305ec158d146100db5780630f235fce146100f7578063150b7a02146101135780636e6bf6d214610143575b5f80fd5b6100c560048036038101906100c09190611ebc565b610245565b6040516100d29190611f01565b60405180910390f35b6100f560048036038101906100f09190611fda565b610326565b005b610111600480360381019061010c9190612077565b6105e6565b005b61012d60048036038101906101289190612161565b6108a0565b60405161013a91906121f4565b60405180910390f35b61015d60048036038101906101589190612077565b610cef565b005b61017960048036038101906101749190611fda565b610faa565b005b61019560048036038101906101909190612262565b611269565b6040516101a291906121f4565b60405180910390f35b6101c560048036038101906101c09190612077565b6112a5565b005b6101e160048036038101906101dc9190611fda565b6115ce565b005b6101fd60048036038101906101f89190612339565b6118fc565b60405161020c9392919061242f565b60405180910390f35b61022f600480360381019061022a9190612464565b611948565b60405161023c91906121f4565b60405180910390f35b5f7f4e2312e0000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061030f57507f150b7a02000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b8061031f575061031e82611ddc565b5b9050919050565b6001600381111561033a576103396123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff16600381111561036c5761036b6123bc565b5b146103ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103a39061257a565b60405180910390fd5b5f600387336002896040516020016103c491906125b8565b6040516020818303038152906040526040516103e09190612624565b602060405180830381855afa1580156103fb573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061041e919061264e565b8888888860405160200161043897969594939291906126de565b6040516020818303038152906040526040516104549190612624565b602060405180830381855afa15801561046f573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146104f9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104f0906127a8565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff0219169083600381111561052f5761052e6123bc565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd738860405161056391906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016105ae949392919061283f565b5f604051808303815f87803b1580156105c5575f80fd5b505af11580156105d7573d5f803e3d5ffd5b50505050505050505050505050565b600160038111156105fa576105f96123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561062c5761062b6123bc565b5b1461066c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106639061257a565b60405180910390fd5b5f600386338787878760405160200161068a96959493929190612895565b6040516020818303038152906040526040516106a69190612624565b602060405180830381855afa1580156106c1573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461074b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610742906127a8565b60405180910390fd5b5f808881526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff164210156107b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107ad90612974565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff021916908360038111156107ec576107eb6123bc565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad192190728760405161082091906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b815260040161086993929190612992565b5f604051808303815f87803b158015610880575f80fd5b505af1158015610892573d5f803e3d5ffd5b505050505050505050505050565b5f8083838101906108b19190612b1a565b90505f60038111156108c6576108c56123bc565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff1660038111156108fb576108fa6123bc565b5b1461093b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161093290612bb5565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff16036109ad576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109a490612c1d565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603610a1f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a1690612c85565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610a91576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8890612d13565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1614610aff576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610af690612d7b565b60405180910390fd5b610b0c8160200151611e45565b15610b4c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b4390612de3565b60405180910390fd5b5f60038260200151888460600151856080015186604001518b604051602001610b7a96959493929190612895565b604051602081830303815290604052604051610b969190612624565b602060405180830381855afa158015610bb1573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff16815260200160016003811115610c0157610c006123bc565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff02191690836003811115610c9857610c976123bc565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f0151604051610cd291906127d5565b60405180910390a163150b7a0260e01b9250505095945050505050565b60016003811115610d0357610d026123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff166003811115610d3557610d346123bc565b5b14610d75576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d6c9061257a565b60405180910390fd5b5f60038633600288604051602001610d8d91906125b8565b604051602081830303815290604052604051610da99190612624565b602060405180830381855afa158015610dc4573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190610de7919061264e565b878787604051602001610dff96959493929190612895565b604051602081830303815290604052604051610e1b9190612624565b602060405180830381855afa158015610e36573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610ec0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610eb7906127a8565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff02191690836003811115610ef657610ef56123bc565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd7387604051610f2a91906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b8152600401610f7393929190612992565b5f604051808303815f87803b158015610f8a575f80fd5b505af1158015610f9c573d5f803e3d5ffd5b505050505050505050505050565b60016003811115610fbe57610fbd6123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115610ff057610fef6123bc565b5b14611030576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110279061257a565b60405180910390fd5b5f60038733888888888860405160200161105097969594939291906126de565b60405160208183030381529060405260405161106c9190612624565b602060405180830381855afa158015611087573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614611111576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611108906127a8565b60405180910390fd5b5f808981526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff1642101561117c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161117390612974565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff021916908360038111156111b2576111b16123bc565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad19219072886040516111e691906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b8152600401611231949392919061283f565b5f604051808303815f87803b158015611248575f80fd5b505af115801561125a573d5f803e3d5ffd5b50505050505050505050505050565b5f6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161129c90612e4b565b60405180910390fd5b600160038111156112b9576112b86123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff1660038111156112eb576112ea6123bc565b5b1461132b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016113229061257a565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611399576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161139090612eb3565b60405180910390fd5b5f60033387876002886040516020016113b291906125b8565b6040516020818303038152906040526040516113ce9190612624565b602060405180830381855afa1580156113e9573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061140c919061264e565b878760405160200161142396959493929190612895565b60405160208183030381529060405260405161143f9190612624565b602060405180830381855afa15801561145a573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146114e4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114db906127a8565b60405180910390fd5b60025f808981526020019081526020015f205f0160186101000a81548160ff0219169083600381111561151a576115196123bc565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08760405161154e91906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b815260040161159793929190612992565b5f604051808303815f87803b1580156115ae575f80fd5b505af11580156115c0573d5f803e3d5ffd5b505050505050505050505050565b600160038111156115e2576115e16123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115611614576116136123bc565b5b14611654576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161164b9061257a565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146116c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116b990612eb3565b60405180910390fd5b5f60033388886002896040516020016116db91906125b8565b6040516020818303038152906040526040516116f79190612624565b602060405180830381855afa158015611712573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190611735919061264e565b88888860405160200161174e97969594939291906126de565b60405160208183030381529060405260405161176a9190612624565b602060405180830381855afa158015611785573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461180f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611806906127a8565b60405180910390fd5b60025f808a81526020019081526020015f205f0160186101000a81548160ff02191690836003811115611845576118446123bc565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08860405161187991906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016118c4949392919061283f565b5f604051808303815f87803b1580156118db575f80fd5b505af11580156118ed573d5f803e3d5ffd5b50505050505050505050505050565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900463ffffffff1690805f0160189054906101000a900460ff16905083565b5f8083838101906119599190612b1a565b90505f600381111561196e5761196d6123bc565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff1660038111156119a3576119a26123bc565b5b146119e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016119da90612f41565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff1603611a55576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611a4c90612c1d565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603611ac7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611abe90612c85565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611b39576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b3090612d13565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614611ba7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b9e90612d7b565b60405180910390fd5b5f8511611be9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611be090612fa9565b60405180910390fd5b611bf68160200151611e45565b15611c36576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c2d90612de3565b60405180910390fd5b5f60038260200151898460600151856080015186604001518c8c604051602001611c6697969594939291906126de565b604051602081830303815290604052604051611c829190612624565b602060405180830381855afa158015611c9d573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff16815260200160016003811115611ced57611cec6123bc565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff02191690836003811115611d8457611d836123bc565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f0151604051611dbe91906127d5565b60405180910390a163f23a6e6160e01b925050509695505050505050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f80823b90505f8111915050919050565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b611e9b81611e67565b8114611ea5575f80fd5b50565b5f81359050611eb681611e92565b92915050565b5f60208284031215611ed157611ed0611e5f565b5b5f611ede84828501611ea8565b91505092915050565b5f8115159050919050565b611efb81611ee7565b82525050565b5f602082019050611f145f830184611ef2565b92915050565b5f819050919050565b611f2c81611f1a565b8114611f36575f80fd5b50565b5f81359050611f4781611f23565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f611f7682611f4d565b9050919050565b611f8681611f6c565b8114611f90575f80fd5b50565b5f81359050611fa181611f7d565b92915050565b5f819050919050565b611fb981611fa7565b8114611fc3575f80fd5b50565b5f81359050611fd481611fb0565b92915050565b5f805f805f805f60e0888a031215611ff557611ff4611e5f565b5b5f6120028a828b01611f39565b97505060206120138a828b01611f93565b96505060406120248a828b01611f39565b95505060606120358a828b01611f39565b94505060806120468a828b01611f93565b93505060a06120578a828b01611fc6565b92505060c06120688a828b01611fc6565b91505092959891949750929550565b5f805f805f8060c0878903121561209157612090611e5f565b5b5f61209e89828a01611f39565b96505060206120af89828a01611f93565b95505060406120c089828a01611f39565b94505060606120d189828a01611f39565b93505060806120e289828a01611f93565b92505060a06120f389828a01611fc6565b9150509295509295509295565b5f80fd5b5f80fd5b5f80fd5b5f8083601f84011261212157612120612100565b5b8235905067ffffffffffffffff81111561213e5761213d612104565b5b60208301915083600182028301111561215a57612159612108565b5b9250929050565b5f805f805f6080868803121561217a57612179611e5f565b5b5f61218788828901611f93565b955050602061219888828901611f93565b94505060406121a988828901611fc6565b935050606086013567ffffffffffffffff8111156121ca576121c9611e63565b5b6121d68882890161210c565b92509250509295509295909350565b6121ee81611e67565b82525050565b5f6020820190506122075f8301846121e5565b92915050565b5f8083601f84011261222257612221612100565b5b8235905067ffffffffffffffff81111561223f5761223e612104565b5b60208301915083602082028301111561225b5761225a612108565b5b9250929050565b5f805f805f805f8060a0898b03121561227e5761227d611e5f565b5b5f61228b8b828c01611f93565b985050602061229c8b828c01611f93565b975050604089013567ffffffffffffffff8111156122bd576122bc611e63565b5b6122c98b828c0161220d565b9650965050606089013567ffffffffffffffff8111156122ec576122eb611e63565b5b6122f88b828c0161220d565b9450945050608089013567ffffffffffffffff81111561231b5761231a611e63565b5b6123278b828c0161210c565b92509250509295985092959890939650565b5f6020828403121561234e5761234d611e5f565b5b5f61235b84828501611f39565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b61239881612364565b82525050565b5f63ffffffff82169050919050565b6123b68161239e565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600481106123fa576123f96123bc565b5b50565b5f81905061240a826123e9565b919050565b5f612419826123fd565b9050919050565b6124298161240f565b82525050565b5f6060820190506124425f83018661238f565b61244f60208301856123ad565b61245c6040830184612420565b949350505050565b5f805f805f8060a0878903121561247e5761247d611e5f565b5b5f61248b89828a01611f93565b965050602061249c89828a01611f93565b95505060406124ad89828a01611fc6565b94505060606124be89828a01611fc6565b935050608087013567ffffffffffffffff8111156124df576124de611e63565b5b6124eb89828a0161210c565b92509250509295509295509295565b5f82825260208201905092915050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520505f8201527f61796d656e7453656e7400000000000000000000000000000000000000000000602082015250565b5f612564602a836124fa565b915061256f8261250a565b604082019050919050565b5f6020820190508181035f83015261259181612558565b9050919050565b5f819050919050565b6125b26125ad82611f1a565b612598565b82525050565b5f6125c382846125a1565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f6125fe826125d2565b61260881856125dc565b93506126188185602086016125e6565b80840191505092915050565b5f61262f82846125f4565b915081905092915050565b5f8151905061264881611f23565b92915050565b5f6020828403121561266357612662611e5f565b5b5f6126708482850161263a565b91505092915050565b5f8160601b9050919050565b5f61268f82612679565b9050919050565b5f6126a082612685565b9050919050565b6126b86126b382611f6c565b612696565b82525050565b5f819050919050565b6126d86126d382611fa7565b6126be565b82525050565b5f6126e9828a6126a7565b6014820191506126f982896126a7565b60148201915061270982886125a1565b60208201915061271982876125a1565b60208201915061272982866126a7565b60148201915061273982856126c7565b60208201915061274982846126c7565b60208201915081905098975050505050505050565b7f496e76616c6964207061796d656e7448617368000000000000000000000000005f82015250565b5f6127926013836124fa565b915061279d8261275e565b602082019050919050565b5f6020820190508181035f8301526127bf81612786565b9050919050565b6127cf81611f1a565b82525050565b5f6020820190506127e85f8301846127c6565b92915050565b6127f781611f6c565b82525050565b61280681611fa7565b82525050565b5f82825260208201905092915050565b50565b5f61282a5f8361280c565b91506128358261281c565b5f82019050919050565b5f60a0820190506128525f8301876127ee565b61285f60208301866127ee565b61286c60408301856127fd565b61287960608301846127fd565b818103608083015261288a8161281f565b905095945050505050565b5f6128a082896126a7565b6014820191506128b082886126a7565b6014820191506128c082876125a1565b6020820191506128d082866125a1565b6020820191506128e082856126a7565b6014820191506128f082846126c7565b602082019150819050979650505050505050565b7f43757272656e742074696d657374616d70206469646e277420657863656564205f8201527f7061796d656e7420726566756e64206c6f636b2074696d650000000000000000602082015250565b5f61295e6038836124fa565b915061296982612904565b604082019050919050565b5f6020820190508181035f83015261298b81612952565b9050919050565b5f6060820190506129a55f8301866127ee565b6129b260208301856127ee565b6129bf60408301846127fd565b949350505050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b612a11826129cb565b810181811067ffffffffffffffff82111715612a3057612a2f6129db565b5b80604052505050565b5f612a42611e56565b9050612a4e8282612a08565b919050565b612a5c8161239e565b8114612a66575f80fd5b50565b5f81359050612a7781612a53565b92915050565b5f60c08284031215612a9257612a916129c7565b5b612a9c60c0612a39565b90505f612aab84828501611f39565b5f830152506020612abe84828501611f93565b6020830152506040612ad284828501611f93565b6040830152506060612ae684828501611f39565b6060830152506080612afa84828501611f39565b60808301525060a0612b0e84828501612a69565b60a08301525092915050565b5f60c08284031215612b2f57612b2e611e5f565b5b5f612b3c84828501612a7d565b91505092915050565b7f4d616b657220455243373231207061796d656e74206d75737420626520556e695f8201527f6e697469616c697a656400000000000000000000000000000000000000000000602082015250565b5f612b9f602a836124fa565b9150612baa82612b45565b604082019050919050565b5f6020820190508181035f830152612bcc81612b93565b9050919050565b7f54616b6572206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f612c07601e836124fa565b9150612c1282612bd3565b602082019050919050565b5f6020820190508181035f830152612c3481612bfb565b9050919050565b7f546f6b656e206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f612c6f601e836124fa565b9150612c7a82612c3b565b602082019050919050565b5f6020820190508181035f830152612c9c81612c63565b9050919050565b7f546f6b656e206164647265737320646f6573206e6f74206d617463682073656e5f8201527f6465720000000000000000000000000000000000000000000000000000000000602082015250565b5f612cfd6023836124fa565b9150612d0882612ca3565b604082019050919050565b5f6020820190508181035f830152612d2a81612cf1565b9050919050565b7f4f70657261746f72206d757374206265207468652073656e64657200000000005f82015250565b5f612d65601b836124fa565b9150612d7082612d31565b602082019050919050565b5f6020820190508181035f830152612d9281612d59565b9050919050565b7f54616b65722063616e6e6f74206265206120636f6e74726163740000000000005f82015250565b5f612dcd601a836124fa565b9150612dd882612d99565b602082019050919050565b5f6020820190508181035f830152612dfa81612dc1565b9050919050565b7f4261746368207472616e7366657273206e6f7420737570706f727465640000005f82015250565b5f612e35601d836124fa565b9150612e4082612e01565b602082019050919050565b5f6020820190508181035f830152612e6281612e29565b9050919050565b7f43616c6c6572206d75737420626520616e20454f4100000000000000000000005f82015250565b5f612e9d6015836124fa565b9150612ea882612e69565b602082019050919050565b5f6020820190508181035f830152612eca81612e91565b9050919050565b7f4d616b65722045524331313535207061796d656e74206d75737420626520556e5f8201527f696e697469616c697a6564000000000000000000000000000000000000000000602082015250565b5f612f2b602b836124fa565b9150612f3682612ed1565b604082019050919050565b5f6020820190508181035f830152612f5881612f1f565b9050919050565b7f56616c7565206d7573742062652067726561746572207468616e2030000000005f82015250565b5f612f93601c836124fa565b9150612f9e82612f5f565b602082019050919050565b5f6020820190508181035f830152612fc081612f87565b905091905056fea26469706673582212208adfd9bc3010e8e9bf1d503f3e439ea5632ee575d696af59d80e0a2268d48d7e64736f6c634300081a0033 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/contract_bytes/swap_contract_bytes b/mm2src/mm2_test_helpers/contract_bytes/swap_contract_bytes new file mode 100644 index 0000000000..fea8557914 --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/swap_contract_bytes @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50611437806100206000396000f3fe60806040526004361061004a5760003560e01c806302ed292b1461004f5780630716326d146100de578063152cf3af1461017b57806346fc0294146101f65780639b415b2a14610294575b600080fd5b34801561005b57600080fd5b506100dc600480360360a081101561007257600080fd5b81019080803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610339565b005b3480156100ea57600080fd5b506101176004803603602081101561010157600080fd5b8101908080359060200190929190505050610867565b60405180846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526020018367ffffffffffffffff1667ffffffffffffffff16815260200182600381111561016557fe5b60ff168152602001935050505060405180910390f35b6101f46004803603608081101561019157600080fd5b8101908080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080356bffffffffffffffffffffffff19169060200190929190803567ffffffffffffffff1690602001909291905050506108bf565b005b34801561020257600080fd5b50610292600480360360a081101561021957600080fd5b81019080803590602001909291908035906020019092919080356bffffffffffffffffffffffff19169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610bd9565b005b610337600480360360c08110156102aa57600080fd5b810190808035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080356bffffffffffffffffffffffff19169060200190929190803567ffffffffffffffff169060200190929190505050610fe2565b005b6001600381111561034657fe5b600080878152602001908152602001600020600001601c9054906101000a900460ff16600381111561037457fe5b1461037e57600080fd5b6000600333836003600288604051602001808281526020019150506040516020818303038152906040526040518082805190602001908083835b602083106103db57805182526020820191506020810190506020830392506103b8565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa15801561041d573d6000803e3d6000fd5b5050506040513d602081101561043257600080fd5b8101908080519060200190929190505050604051602001808281526020019150506040516020818303038152906040526040518082805190602001908083835b602083106104955780518252602082019150602081019050602083039250610472565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa1580156104d7573d6000803e3d6000fd5b5050506040515160601b8689604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b602083106105fc57805182526020820191506020810190506020830392506105d9565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa15801561063e573d6000803e3d6000fd5b5050506040515160601b905060008087815260200190815260200160002060000160009054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461069657600080fd5b6002600080888152602001908152602001600020600001601c6101000a81548160ff021916908360038111156106c857fe5b0217905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561074e573373ffffffffffffffffffffffffffffffffffffffff166108fc869081150290604051600060405180830381858888f19350505050158015610748573d6000803e3d6000fd5b50610820565b60008390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156107da57600080fd5b505af11580156107ee573d6000803e3d6000fd5b505050506040513d602081101561080457600080fd5b810190808051906020019092919050505061081e57600080fd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e8685604051808381526020018281526020019250505060405180910390a1505050505050565b60006020528060005260406000206000915090508060000160009054906101000a900460601b908060000160149054906101000a900467ffffffffffffffff169080600001601c9054906101000a900460ff16905083565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141580156108fc5750600034115b801561094057506000600381111561091057fe5b600080868152602001908152602001600020600001601c9054906101000a900460ff16600381111561093e57fe5b145b61094957600080fd5b60006003843385600034604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b60208310610a6c5780518252602082019150602081019050602083039250610a49565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa158015610aae573d6000803e3d6000fd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff16815260200160016003811115610af757fe5b81525060008087815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c021790555060208201518160000160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550604082015181600001601c6101000a81548160ff02191690836003811115610b9357fe5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57856040518082815260200191505060405180910390a15050505050565b60016003811115610be657fe5b600080878152602001908152602001600020600001601c9054906101000a900460ff166003811115610c1457fe5b14610c1e57600080fd5b600060038233868689604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b60208310610d405780518252602082019150602081019050602083039250610d1d565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa158015610d82573d6000803e3d6000fd5b5050506040515160601b905060008087815260200190815260200160002060000160009054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916148015610e10575060008087815260200190815260200160002060000160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b610e1957600080fd5b6003600080888152602001908152602001600020600001601c6101000a81548160ff02191690836003811115610e4b57fe5b0217905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610ed1573373ffffffffffffffffffffffffffffffffffffffff166108fc869081150290604051600060405180830381858888f19350505050158015610ecb573d6000803e3d6000fd5b50610fa3565b60008390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015610f5d57600080fd5b505af1158015610f71573d6000803e3d6000fd5b505050506040513d6020811015610f8757600080fd5b8101908080519060200190929190505050610fa157600080fd5b505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba866040518082815260200191505060405180910390a1505050505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561101f5750600085115b801561106357506000600381111561103357fe5b600080888152602001908152602001600020600001601c9054906101000a900460ff16600381111561106157fe5b145b61106c57600080fd5b60006003843385888a604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b6020831061118e578051825260208201915060208101905060208303925061116b565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa1580156111d0573d6000803e3d6000fd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff1681526020016001600381111561121957fe5b81525060008089815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c021790555060208201518160000160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550604082015181600001601c6101000a81548160ff021916908360038111156112b557fe5b021790555090505060008590508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308a6040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019350505050602060405180830381600087803b15801561137d57600080fd5b505af1158015611391573d6000803e3d6000fd5b505050506040513d60208110156113a757600080fd5b81019080805190602001909291905050506113c157600080fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040518082815260200191505060405180910390a1505050505050505056fea265627a7a723158208c83db436905afce0b7be1012be64818c49323c12d451fe2ab6bce76ff6421c964736f6c63430005110032 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/contract_bytes/taker_swap_v2_bytes b/mm2src/mm2_test_helpers/contract_bytes/taker_swap_v2_bytes new file mode 100644 index 0000000000..ea4cd8c817 --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/taker_swap_v2_bytes @@ -0,0 +1 @@ +6080604052348015600e575f80fd5b50612ffd8061001c5f395ff3fe608060405234801561000f575f80fd5b50600436106100a7575f3560e01c8063b27e46fb1161006f578063b27e46fb1461015f578063bc197c811461017b578063c8d9009b146101ab578063c92cd12d146101c7578063efccb9eb146101e3578063f23a6e6114610215576100a7565b806301ffc9a7146100ab57806305ec158d146100db5780630f235fce146100f7578063150b7a02146101135780636e6bf6d214610143575b5f80fd5b6100c560048036038101906100c09190611ebc565b610245565b6040516100d29190611f01565b60405180910390f35b6100f560048036038101906100f09190611fda565b610326565b005b610111600480360381019061010c9190612077565b6105e6565b005b61012d60048036038101906101289190612161565b6108a0565b60405161013a91906121f4565b60405180910390f35b61015d60048036038101906101589190612077565b610cef565b005b61017960048036038101906101749190611fda565b610faa565b005b61019560048036038101906101909190612262565b611269565b6040516101a291906121f4565b60405180910390f35b6101c560048036038101906101c09190612077565b6112a5565b005b6101e160048036038101906101dc9190611fda565b6115ce565b005b6101fd60048036038101906101f89190612339565b6118fc565b60405161020c9392919061242f565b60405180910390f35b61022f600480360381019061022a9190612464565b611948565b60405161023c91906121f4565b60405180910390f35b5f7f4e2312e0000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061030f57507f150b7a02000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b8061031f575061031e82611ddc565b5b9050919050565b6001600381111561033a576103396123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff16600381111561036c5761036b6123bc565b5b146103ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103a39061257a565b60405180910390fd5b5f600387336002896040516020016103c491906125b8565b6040516020818303038152906040526040516103e09190612624565b602060405180830381855afa1580156103fb573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061041e919061264e565b8888888860405160200161043897969594939291906126de565b6040516020818303038152906040526040516104549190612624565b602060405180830381855afa15801561046f573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146104f9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104f0906127a8565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff0219169083600381111561052f5761052e6123bc565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd738860405161056391906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016105ae949392919061283f565b5f604051808303815f87803b1580156105c5575f80fd5b505af11580156105d7573d5f803e3d5ffd5b50505050505050505050505050565b600160038111156105fa576105f96123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561062c5761062b6123bc565b5b1461066c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106639061257a565b60405180910390fd5b5f600386338787878760405160200161068a96959493929190612895565b6040516020818303038152906040526040516106a69190612624565b602060405180830381855afa1580156106c1573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461074b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610742906127a8565b60405180910390fd5b5f808881526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff164210156107b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107ad90612974565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff021916908360038111156107ec576107eb6123bc565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad192190728760405161082091906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b815260040161086993929190612992565b5f604051808303815f87803b158015610880575f80fd5b505af1158015610892573d5f803e3d5ffd5b505050505050505050505050565b5f8083838101906108b19190612b1a565b90505f60038111156108c6576108c56123bc565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff1660038111156108fb576108fa6123bc565b5b1461093b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161093290612bb5565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff16036109ad576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109a490612c1d565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603610a1f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a1690612c85565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610a91576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8890612d13565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1614610aff576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610af690612d7b565b60405180910390fd5b610b0c8160200151611e45565b15610b4c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b4390612de3565b60405180910390fd5b5f60038260200151888460600151856080015186604001518b604051602001610b7a96959493929190612895565b604051602081830303815290604052604051610b969190612624565b602060405180830381855afa158015610bb1573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff16815260200160016003811115610c0157610c006123bc565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff02191690836003811115610c9857610c976123bc565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f0151604051610cd291906127d5565b60405180910390a163150b7a0260e01b9250505095945050505050565b60016003811115610d0357610d026123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff166003811115610d3557610d346123bc565b5b14610d75576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d6c9061257a565b60405180910390fd5b5f60038633600288604051602001610d8d91906125b8565b604051602081830303815290604052604051610da99190612624565b602060405180830381855afa158015610dc4573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190610de7919061264e565b878787604051602001610dff96959493929190612895565b604051602081830303815290604052604051610e1b9190612624565b602060405180830381855afa158015610e36573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610ec0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610eb7906127a8565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff02191690836003811115610ef657610ef56123bc565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd7387604051610f2a91906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b8152600401610f7393929190612992565b5f604051808303815f87803b158015610f8a575f80fd5b505af1158015610f9c573d5f803e3d5ffd5b505050505050505050505050565b60016003811115610fbe57610fbd6123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115610ff057610fef6123bc565b5b14611030576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110279061257a565b60405180910390fd5b5f60038733888888888860405160200161105097969594939291906126de565b60405160208183030381529060405260405161106c9190612624565b602060405180830381855afa158015611087573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614611111576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611108906127a8565b60405180910390fd5b5f808981526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff1642101561117c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161117390612974565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff021916908360038111156111b2576111b16123bc565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad19219072886040516111e691906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b8152600401611231949392919061283f565b5f604051808303815f87803b158015611248575f80fd5b505af115801561125a573d5f803e3d5ffd5b50505050505050505050505050565b5f6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161129c90612e4b565b60405180910390fd5b600160038111156112b9576112b86123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff1660038111156112eb576112ea6123bc565b5b1461132b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016113229061257a565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611399576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161139090612eb3565b60405180910390fd5b5f60033387876002886040516020016113b291906125b8565b6040516020818303038152906040526040516113ce9190612624565b602060405180830381855afa1580156113e9573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061140c919061264e565b878760405160200161142396959493929190612895565b60405160208183030381529060405260405161143f9190612624565b602060405180830381855afa15801561145a573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146114e4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114db906127a8565b60405180910390fd5b60025f808981526020019081526020015f205f0160186101000a81548160ff0219169083600381111561151a576115196123bc565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08760405161154e91906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b815260040161159793929190612992565b5f604051808303815f87803b1580156115ae575f80fd5b505af11580156115c0573d5f803e3d5ffd5b505050505050505050505050565b600160038111156115e2576115e16123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115611614576116136123bc565b5b14611654576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161164b9061257a565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146116c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116b990612eb3565b60405180910390fd5b5f60033388886002896040516020016116db91906125b8565b6040516020818303038152906040526040516116f79190612624565b602060405180830381855afa158015611712573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190611735919061264e565b88888860405160200161174e97969594939291906126de565b60405160208183030381529060405260405161176a9190612624565b602060405180830381855afa158015611785573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461180f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611806906127a8565b60405180910390fd5b60025f808a81526020019081526020015f205f0160186101000a81548160ff02191690836003811115611845576118446123bc565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08860405161187991906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016118c4949392919061283f565b5f604051808303815f87803b1580156118db575f80fd5b505af11580156118ed573d5f803e3d5ffd5b50505050505050505050505050565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900463ffffffff1690805f0160189054906101000a900460ff16905083565b5f8083838101906119599190612b1a565b90505f600381111561196e5761196d6123bc565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff1660038111156119a3576119a26123bc565b5b146119e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016119da90612f41565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff1603611a55576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611a4c90612c1d565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603611ac7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611abe90612c85565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611b39576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b3090612d13565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614611ba7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b9e90612d7b565b60405180910390fd5b5f8511611be9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611be090612fa9565b60405180910390fd5b611bf68160200151611e45565b15611c36576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c2d90612de3565b60405180910390fd5b5f60038260200151898460600151856080015186604001518c8c604051602001611c6697969594939291906126de565b604051602081830303815290604052604051611c829190612624565b602060405180830381855afa158015611c9d573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff16815260200160016003811115611ced57611cec6123bc565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff02191690836003811115611d8457611d836123bc565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f0151604051611dbe91906127d5565b60405180910390a163f23a6e6160e01b925050509695505050505050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f80823b90505f8111915050919050565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b611e9b81611e67565b8114611ea5575f80fd5b50565b5f81359050611eb681611e92565b92915050565b5f60208284031215611ed157611ed0611e5f565b5b5f611ede84828501611ea8565b91505092915050565b5f8115159050919050565b611efb81611ee7565b82525050565b5f602082019050611f145f830184611ef2565b92915050565b5f819050919050565b611f2c81611f1a565b8114611f36575f80fd5b50565b5f81359050611f4781611f23565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f611f7682611f4d565b9050919050565b611f8681611f6c565b8114611f90575f80fd5b50565b5f81359050611fa181611f7d565b92915050565b5f819050919050565b611fb981611fa7565b8114611fc3575f80fd5b50565b5f81359050611fd481611fb0565b92915050565b5f805f805f805f60e0888a031215611ff557611ff4611e5f565b5b5f6120028a828b01611f39565b97505060206120138a828b01611f93565b96505060406120248a828b01611f39565b95505060606120358a828b01611f39565b94505060806120468a828b01611f93565b93505060a06120578a828b01611fc6565b92505060c06120688a828b01611fc6565b91505092959891949750929550565b5f805f805f8060c0878903121561209157612090611e5f565b5b5f61209e89828a01611f39565b96505060206120af89828a01611f93565b95505060406120c089828a01611f39565b94505060606120d189828a01611f39565b93505060806120e289828a01611f93565b92505060a06120f389828a01611fc6565b9150509295509295509295565b5f80fd5b5f80fd5b5f80fd5b5f8083601f84011261212157612120612100565b5b8235905067ffffffffffffffff81111561213e5761213d612104565b5b60208301915083600182028301111561215a57612159612108565b5b9250929050565b5f805f805f6080868803121561217a57612179611e5f565b5b5f61218788828901611f93565b955050602061219888828901611f93565b94505060406121a988828901611fc6565b935050606086013567ffffffffffffffff8111156121ca576121c9611e63565b5b6121d68882890161210c565b92509250509295509295909350565b6121ee81611e67565b82525050565b5f6020820190506122075f8301846121e5565b92915050565b5f8083601f84011261222257612221612100565b5b8235905067ffffffffffffffff81111561223f5761223e612104565b5b60208301915083602082028301111561225b5761225a612108565b5b9250929050565b5f805f805f805f8060a0898b03121561227e5761227d611e5f565b5b5f61228b8b828c01611f93565b985050602061229c8b828c01611f93565b975050604089013567ffffffffffffffff8111156122bd576122bc611e63565b5b6122c98b828c0161220d565b9650965050606089013567ffffffffffffffff8111156122ec576122eb611e63565b5b6122f88b828c0161220d565b9450945050608089013567ffffffffffffffff81111561231b5761231a611e63565b5b6123278b828c0161210c565b92509250509295985092959890939650565b5f6020828403121561234e5761234d611e5f565b5b5f61235b84828501611f39565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b61239881612364565b82525050565b5f63ffffffff82169050919050565b6123b68161239e565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600481106123fa576123f96123bc565b5b50565b5f81905061240a826123e9565b919050565b5f612419826123fd565b9050919050565b6124298161240f565b82525050565b5f6060820190506124425f83018661238f565b61244f60208301856123ad565b61245c6040830184612420565b949350505050565b5f805f805f8060a0878903121561247e5761247d611e5f565b5b5f61248b89828a01611f93565b965050602061249c89828a01611f93565b95505060406124ad89828a01611fc6565b94505060606124be89828a01611fc6565b935050608087013567ffffffffffffffff8111156124df576124de611e63565b5b6124eb89828a0161210c565b92509250509295509295509295565b5f82825260208201905092915050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520505f8201527f61796d656e7453656e7400000000000000000000000000000000000000000000602082015250565b5f612564602a836124fa565b915061256f8261250a565b604082019050919050565b5f6020820190508181035f83015261259181612558565b9050919050565b5f819050919050565b6125b26125ad82611f1a565b612598565b82525050565b5f6125c382846125a1565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f6125fe826125d2565b61260881856125dc565b93506126188185602086016125e6565b80840191505092915050565b5f61262f82846125f4565b915081905092915050565b5f8151905061264881611f23565b92915050565b5f6020828403121561266357612662611e5f565b5b5f6126708482850161263a565b91505092915050565b5f8160601b9050919050565b5f61268f82612679565b9050919050565b5f6126a082612685565b9050919050565b6126b86126b382611f6c565b612696565b82525050565b5f819050919050565b6126d86126d382611fa7565b6126be565b82525050565b5f6126e9828a6126a7565b6014820191506126f982896126a7565b60148201915061270982886125a1565b60208201915061271982876125a1565b60208201915061272982866126a7565b60148201915061273982856126c7565b60208201915061274982846126c7565b60208201915081905098975050505050505050565b7f496e76616c6964207061796d656e7448617368000000000000000000000000005f82015250565b5f6127926013836124fa565b915061279d8261275e565b602082019050919050565b5f6020820190508181035f8301526127bf81612786565b9050919050565b6127cf81611f1a565b82525050565b5f6020820190506127e85f8301846127c6565b92915050565b6127f781611f6c565b82525050565b61280681611fa7565b82525050565b5f82825260208201905092915050565b50565b5f61282a5f8361280c565b91506128358261281c565b5f82019050919050565b5f60a0820190506128525f8301876127ee565b61285f60208301866127ee565b61286c60408301856127fd565b61287960608301846127fd565b818103608083015261288a8161281f565b905095945050505050565b5f6128a082896126a7565b6014820191506128b082886126a7565b6014820191506128c082876125a1565b6020820191506128d082866125a1565b6020820191506128e082856126a7565b6014820191506128f082846126c7565b602082019150819050979650505050505050565b7f43757272656e742074696d657374616d70206469646e277420657863656564205f8201527f7061796d656e7420726566756e64206c6f636b2074696d650000000000000000602082015250565b5f61295e6038836124fa565b915061296982612904565b604082019050919050565b5f6020820190508181035f83015261298b81612952565b9050919050565b5f6060820190506129a55f8301866127ee565b6129b260208301856127ee565b6129bf60408301846127fd565b949350505050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b612a11826129cb565b810181811067ffffffffffffffff82111715612a3057612a2f6129db565b5b80604052505050565b5f612a42611e56565b9050612a4e8282612a08565b919050565b612a5c8161239e565b8114612a66575f80fd5b50565b5f81359050612a7781612a53565b92915050565b5f60c08284031215612a9257612a916129c7565b5b612a9c60c0612a39565b90505f612aab84828501611f39565b5f830152506020612abe84828501611f93565b6020830152506040612ad284828501611f93565b6040830152506060612ae684828501611f39565b6060830152506080612afa84828501611f39565b60808301525060a0612b0e84828501612a69565b60a08301525092915050565b5f60c08284031215612b2f57612b2e611e5f565b5b5f612b3c84828501612a7d565b91505092915050565b7f4d616b657220455243373231207061796d656e74206d75737420626520556e695f8201527f6e697469616c697a656400000000000000000000000000000000000000000000602082015250565b5f612b9f602a836124fa565b9150612baa82612b45565b604082019050919050565b5f6020820190508181035f830152612bcc81612b93565b9050919050565b7f54616b6572206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f612c07601e836124fa565b9150612c1282612bd3565b602082019050919050565b5f6020820190508181035f830152612c3481612bfb565b9050919050565b7f546f6b656e206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f612c6f601e836124fa565b9150612c7a82612c3b565b602082019050919050565b5f6020820190508181035f830152612c9c81612c63565b9050919050565b7f546f6b656e206164647265737320646f6573206e6f74206d617463682073656e5f8201527f6465720000000000000000000000000000000000000000000000000000000000602082015250565b5f612cfd6023836124fa565b9150612d0882612ca3565b604082019050919050565b5f6020820190508181035f830152612d2a81612cf1565b9050919050565b7f4f70657261746f72206d757374206265207468652073656e64657200000000005f82015250565b5f612d65601b836124fa565b9150612d7082612d31565b602082019050919050565b5f6020820190508181035f830152612d9281612d59565b9050919050565b7f54616b65722063616e6e6f74206265206120636f6e74726163740000000000005f82015250565b5f612dcd601a836124fa565b9150612dd882612d99565b602082019050919050565b5f6020820190508181035f830152612dfa81612dc1565b9050919050565b7f4261746368207472616e7366657273206e6f7420737570706f727465640000005f82015250565b5f612e35601d836124fa565b9150612e4082612e01565b602082019050919050565b5f6020820190508181035f830152612e6281612e29565b9050919050565b7f43616c6c6572206d75737420626520616e20454f4100000000000000000000005f82015250565b5f612e9d6015836124fa565b9150612ea882612e69565b602082019050919050565b5f6020820190508181035f830152612eca81612e91565b9050919050565b7f4d616b65722045524331313535207061796d656e74206d75737420626520556e5f8201527f696e697469616c697a6564000000000000000000000000000000000000000000602082015250565b5f612f2b602b836124fa565b9150612f3682612ed1565b604082019050919050565b5f6020820190508181035f830152612f5881612f1f565b9050919050565b7f56616c7565206d7573742062652067726561746572207468616e2030000000005f82015250565b5f612f93601c836124fa565b9150612f9e82612f5f565b602082019050919050565b5f6020820190508181035f830152612fc081612f87565b905091905056fea26469706673582212208adfd9bc3010e8e9bf1d503f3e439ea5632ee575d696af59d80e0a2268d48d7e64736f6c634300081a0033 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/contract_bytes/watchers_swap_contract_bytes b/mm2src/mm2_test_helpers/contract_bytes/watchers_swap_contract_bytes new file mode 100644 index 0000000000..1b00a95ba1 --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/watchers_swap_contract_bytes @@ -0,0 +1 @@ +608060405234801561000f575f80fd5b50612aa48061001d5f395ff3fe608060405260043610610085575f3560e01c806346fc02941161005857806346fc0294146101275780636a3227861461014f5780639b415b2a1461016b578063b5985c4d14610193578063cd1dde34146101bb57610085565b806302ed292b146100895780630716326d146100b15780630971fd54146100ef578063152cf3af1461010b575b5f80fd5b348015610094575f80fd5b506100af60048036038101906100aa9190611e1d565b6101e3565b005b3480156100bc575f80fd5b506100d760048036038101906100d29190611e94565b610518565b6040516100e693929190611f8e565b60405180910390f35b6101096004803603810190610104919061206f565b610568565b005b6101256004803603810190610120919061210c565b610787565b005b348015610132575f80fd5b5061014d60048036038101906101489190612170565b61099d565b005b610169600480360381019061016491906121e7565b610c4d565b005b348015610176575f80fd5b50610191600480360381019061018c91906122ab565b610f61565b005b34801561019e575f80fd5b506101b960048036038101906101b49190612334565b611203565b005b3480156101c6575f80fd5b506101e160048036038101906101dc91906123f8565b611887565b005b600160038111156101f7576101f6611f1b565b5b5f808781526020019081526020015f205f01601c9054906101000a900460ff16600381111561022957610228611f1b565b5b14610232575f80fd5b5f60033383600360028860405160200161024c91906124dc565b6040516020818303038152906040526040516102689190612562565b602060405180830381855afa158015610283573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906102a6919061258c565b6040516020016102b691906124dc565b6040516020818303038152906040526040516102d29190612562565b602060405180830381855afa1580156102ed573d5f803e3d5ffd5b5050506040515160601b868960405160200161030d95949392919061263c565b6040516020818303038152906040526040516103299190612562565b602060405180830381855afa158015610344573d5f803e3d5ffd5b5050506040515160601b90505f808781526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610397575f80fd5b60025f808881526020019081526020015f205f01601c6101000a81548160ff021916908360038111156103cd576103cc611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361044e573373ffffffffffffffffffffffffffffffffffffffff166108fc8690811502906040515f60405180830381858888f19350505050158015610448573d5f803e3d5ffd5b506104d7565b5f8390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b815260040161048d9291906126b8565b6020604051808303815f875af11580156104a9573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104cd91906126f3565b6104d5575f80fd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e868560405161050892919061272d565b60405180910390a1505050505050565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900467ffffffffffffffff1690805f01601c9054906101000a900460ff16905083565b5f73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16141580156105a357505f34115b80156105f157505f60038111156105bd576105bc611f1b565b5b5f808981526020019081526020015f205f01601c9054906101000a900460ff1660038111156105ef576105ee611f1b565b5b145b6105f9575f80fd5b5f60038733885f3489898960405160200161061b9897969594939291906127e7565b6040516020818303038152906040526040516106379190612562565b602060405180830381855afa158015610652573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018667ffffffffffffffff168152602001600160038111156106a2576106a1611f1b565b5b8152505f808a81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561073e5761073d611f1b565b5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040516107759190612878565b60405180910390a15050505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141580156107c257505f34115b801561081057505f60038111156107dc576107db611f1b565b5b5f808681526020019081526020015f205f01601c9054906101000a900460ff16600381111561080e5761080d611f1b565b5b145b610818575f80fd5b5f60038433855f3460405160200161083495949392919061263c565b6040516020818303038152906040526040516108509190612562565b602060405180830381855afa15801561086b573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff168152602001600160038111156108bb576108ba611f1b565b5b8152505f808781526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561095757610956611f1b565b5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad578560405161098e9190612878565b60405180910390a15050505050565b600160038111156109b1576109b0611f1b565b5b5f808781526020019081526020015f205f01601c9054906101000a900460ff1660038111156109e3576109e2611f1b565b5b146109ec575f80fd5b5f60038233868689604051602001610a0895949392919061263c565b604051602081830303815290604052604051610a249190612562565b602060405180830381855afa158015610a3f573d5f803e3d5ffd5b5050506040515160601b90505f808781526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916148015610ac657505f808781526020019081526020015f205f0160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b610ace575f80fd5b60035f808881526020019081526020015f205f01601c6101000a81548160ff02191690836003811115610b0457610b03611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610b85573373ffffffffffffffffffffffffffffffffffffffff166108fc8690811502906040515f60405180830381858888f19350505050158015610b7f573d5f803e3d5ffd5b50610c0e565b5f8390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401610bc49291906126b8565b6020604051808303815f875af1158015610be0573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c0491906126f3565b610c0c575f80fd5b505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba86604051610c3d9190612878565b60405180910390a1505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614158015610c8857505f88115b8015610cd657505f6003811115610ca257610ca1611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff166003811115610cd457610cd3611f1b565b5b145b610cde575f80fd5b5f6003811115610cf157610cf0611f1b565b5b836003811115610d0457610d03611f1b565b5b14158015610d365750600380811115610d2057610d1f611f1b565b5b836003811115610d3357610d32611f1b565b5b14155b15610d4757803414610d46575f80fd5b5b5f60038733888b8d898989604051602001610d699897969594939291906127e7565b604051602081830303815290604052604051610d859190612562565b602060405180830381855afa158015610da0573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018667ffffffffffffffff16815260200160016003811115610df057610def611f1b565b5b8152505f808c81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff02191690836003811115610e8c57610e8b611f1b565b5b02179055509050505f8890508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308d6040518463ffffffff1660e01b8152600401610ed593929190612891565b6020604051808303815f875af1158015610ef1573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f1591906126f3565b610f1d575f80fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad578b604051610f4c9190612878565b60405180910390a15050505050505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614158015610f9c57505f85115b8015610fea57505f6003811115610fb657610fb5611f1b565b5b5f808881526020019081526020015f205f01601c9054906101000a900460ff166003811115610fe857610fe7611f1b565b5b145b610ff2575f80fd5b5f6003843385888a60405160200161100e95949392919061263c565b60405160208183030381529060405260405161102a9190612562565b602060405180830381855afa158015611045573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff1681526020016001600381111561109557611094611f1b565b5b8152505f808981526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561113157611130611f1b565b5b02179055509050505f8590508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308a6040518463ffffffff1660e01b815260040161117a93929190612891565b6020604051808303815f875af1158015611196573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111ba91906126f3565b6111c2575f80fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040516111f19190612878565b60405180910390a15050505050505050565b6001600381111561121757611216611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff16600381111561124957611248611f1b565b5b14611289576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161128090612920565b60405180910390fd5b5f60038587600360028c6040516020016112a391906124dc565b6040516020818303038152906040526040516112bf9190612562565b602060405180830381855afa1580156112da573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906112fd919061258c565b60405160200161130d91906124dc565b6040516020818303038152906040526040516113299190612562565b602060405180830381855afa158015611344573d5f803e3d5ffd5b5050506040515160601b8a8d89898960405160200161136a9897969594939291906127e7565b6040516020818303038152906040526040516113869190612562565b602060405180830381855afa1580156113a1573d5f803e3d5ffd5b5050506040515160601b90505f808b81526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461142b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161142290612988565b60405180910390fd5b60025f808c81526020019081526020015f205f01601c6101000a81548160ff0219169083600381111561146157611460611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff160361159e575f8060038111156114ad576114ac611f1b565b5b8560038111156114c0576114bf611f1b565b5b1480156114cb575083155b6114e057828a6114db91906129d3565b6114e2565b895b90508573ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015611527573d5f803e3d5ffd5b5060038081111561153b5761153a611f1b565b5b85600381111561154e5761154d611f1b565b5b03611598573373ffffffffffffffffffffffffffffffffffffffff166108fc8490811502906040515f60405180830381858888f19350505050158015611596573d5f803e3d5ffd5b505b50611786565b5f6003808111156115b2576115b1611f1b565b5b8560038111156115c5576115c4611f1b565b5b146115d057896115dd565b828a6115dc91906129d3565b5b90505f8890508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb88846040518363ffffffff1660e01b815260040161161e9291906126b8565b6020604051808303815f875af115801561163a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061165e91906126f3565b61169d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161169490612a50565b60405180910390fd5b6003808111156116b0576116af611f1b565b5b8660038111156116c3576116c2611f1b565b5b03611783578073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33866040518363ffffffff1660e01b81526004016117039291906126b8565b6020604051808303815f875af115801561171f573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061174391906126f3565b611782576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161177990612a50565b60405180910390fd5b5b50505b6002600381111561179a57611799611f1b565b5b8460038111156117ad576117ac611f1b565b5b036117f7578573ffffffffffffffffffffffffffffffffffffffff166108fc8390811502906040515f60405180830381858888f193505050501580156117f5573d5f803e3d5ffd5b505b8215611842573373ffffffffffffffffffffffffffffffffffffffff166108fc8390811502906040515f60405180830381858888f19350505050158015611840573d5f803e3d5ffd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e8a8960405161187392919061272d565b60405180910390a150505050505050505050565b6001600381111561189b5761189a611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff1660038111156118cd576118cc611f1b565b5b146118d6575f80fd5b5f600385878a8a8d8989896040516020016118f89897969594939291906127e7565b6040516020818303038152906040526040516119149190612562565b602060405180830381855afa15801561192f573d5f803e3d5ffd5b5050506040515160601b90505f808b81526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161480156119b657505f808b81526020019081526020015f205f0160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b6119be575f80fd5b60035f808c81526020019081526020015f205f01601c6101000a81548160ff021916908360038111156119f4576119f3611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1603611b27575f806003811115611a4057611a3f611f1b565b5b856003811115611a5357611a52611f1b565b5b14611a6957828a611a6491906129d3565b611a6b565b895b90508673ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015611ab0573d5f803e3d5ffd5b505f6003811115611ac457611ac3611f1b565b5b856003811115611ad757611ad6611f1b565b5b14611b21573373ffffffffffffffffffffffffffffffffffffffff166108fc8490811502906040515f60405180830381858888f19350505050158015611b1f573d5f803e3d5ffd5b505b50611d16565b5f600380811115611b3b57611b3a611f1b565b5b856003811115611b4e57611b4d611f1b565b5b14611b595789611b66565b828a611b6591906129d3565b5b90505f8890508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb89846040518363ffffffff1660e01b8152600401611ba79291906126b8565b6020604051808303815f875af1158015611bc3573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611be791906126f3565b611bef575f80fd5b600380811115611c0257611c01611f1b565b5b866003811115611c1557611c14611f1b565b5b03611ca2578073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33866040518363ffffffff1660e01b8152600401611c559291906126b8565b6020604051808303815f875af1158015611c71573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c9591906126f3565b611c9d575f80fd5b611d13565b5f6003811115611cb557611cb4611f1b565b5b866003811115611cc857611cc7611f1b565b5b14611d12573373ffffffffffffffffffffffffffffffffffffffff166108fc8590811502906040515f60405180830381858888f19350505050158015611d10573d5f803e3d5ffd5b505b5b50505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba8a604051611d459190612878565b60405180910390a150505050505050505050565b5f80fd5b5f819050919050565b611d6f81611d5d565b8114611d79575f80fd5b50565b5f81359050611d8a81611d66565b92915050565b5f819050919050565b611da281611d90565b8114611dac575f80fd5b50565b5f81359050611dbd81611d99565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f611dec82611dc3565b9050919050565b611dfc81611de2565b8114611e06575f80fd5b50565b5f81359050611e1781611df3565b92915050565b5f805f805f60a08688031215611e3657611e35611d59565b5b5f611e4388828901611d7c565b9550506020611e5488828901611daf565b9450506040611e6588828901611d7c565b9350506060611e7688828901611e09565b9250506080611e8788828901611e09565b9150509295509295909350565b5f60208284031215611ea957611ea8611d59565b5b5f611eb684828501611d7c565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b611ef381611ebf565b82525050565b5f67ffffffffffffffff82169050919050565b611f1581611ef9565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60048110611f5957611f58611f1b565b5b50565b5f819050611f6982611f48565b919050565b5f611f7882611f5c565b9050919050565b611f8881611f6e565b82525050565b5f606082019050611fa15f830186611eea565b611fae6020830185611f0c565b611fbb6040830184611f7f565b949350505050565b611fcc81611ebf565b8114611fd6575f80fd5b50565b5f81359050611fe781611fc3565b92915050565b611ff681611ef9565b8114612000575f80fd5b50565b5f8135905061201181611fed565b92915050565b60048110612023575f80fd5b50565b5f8135905061203481612017565b92915050565b5f8115159050919050565b61204e8161203a565b8114612058575f80fd5b50565b5f8135905061206981612045565b92915050565b5f805f805f805f60e0888a03121561208a57612089611d59565b5b5f6120978a828b01611d7c565b97505060206120a88a828b01611e09565b96505060406120b98a828b01611fd9565b95505060606120ca8a828b01612003565b94505060806120db8a828b01612026565b93505060a06120ec8a828b0161205b565b92505060c06120fd8a828b01611daf565b91505092959891949750929550565b5f805f806080858703121561212457612123611d59565b5b5f61213187828801611d7c565b945050602061214287828801611e09565b935050604061215387828801611fd9565b925050606061216487828801612003565b91505092959194509250565b5f805f805f60a0868803121561218957612188611d59565b5b5f61219688828901611d7c565b95505060206121a788828901611daf565b94505060406121b888828901611fd9565b93505060606121c988828901611e09565b92505060806121da88828901611e09565b9150509295509295909350565b5f805f805f805f805f6101208a8c03121561220557612204611d59565b5b5f6122128c828d01611d7c565b99505060206122238c828d01611daf565b98505060406122348c828d01611e09565b97505060606122458c828d01611e09565b96505060806122568c828d01611fd9565b95505060a06122678c828d01612003565b94505060c06122788c828d01612026565b93505060e06122898c828d0161205b565b92505061010061229b8c828d01611daf565b9150509295985092959850929598565b5f805f805f8060c087890312156122c5576122c4611d59565b5b5f6122d289828a01611d7c565b96505060206122e389828a01611daf565b95505060406122f489828a01611e09565b945050606061230589828a01611e09565b935050608061231689828a01611fd9565b92505060a061232789828a01612003565b9150509295509295509295565b5f805f805f805f805f6101208a8c03121561235257612351611d59565b5b5f61235f8c828d01611d7c565b99505060206123708c828d01611daf565b98505060406123818c828d01611d7c565b97505060606123928c828d01611e09565b96505060806123a38c828d01611e09565b95505060a06123b48c828d01611e09565b94505060c06123c58c828d01612026565b93505060e06123d68c828d0161205b565b9250506101006123e88c828d01611daf565b9150509295985092959850929598565b5f805f805f805f805f6101208a8c03121561241657612415611d59565b5b5f6124238c828d01611d7c565b99505060206124348c828d01611daf565b98505060406124458c828d01611fd9565b97505060606124568c828d01611e09565b96505060806124678c828d01611e09565b95505060a06124788c828d01611e09565b94505060c06124898c828d01612026565b93505060e061249a8c828d0161205b565b9250506101006124ac8c828d01611daf565b9150509295985092959850929598565b5f819050919050565b6124d66124d182611d5d565b6124bc565b82525050565b5f6124e782846124c5565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b5f5b8381101561252757808201518184015260208101905061250c565b5f8484015250505050565b5f61253c826124f6565b6125468185612500565b935061255681856020860161250a565b80840191505092915050565b5f61256d8284612532565b915081905092915050565b5f8151905061258681611d66565b92915050565b5f602082840312156125a1576125a0611d59565b5b5f6125ae84828501612578565b91505092915050565b5f8160601b9050919050565b5f6125cd826125b7565b9050919050565b5f6125de826125c3565b9050919050565b6125f66125f182611de2565b6125d4565b82525050565b5f819050919050565b61261661261182611ebf565b6125fc565b82525050565b5f819050919050565b61263661263182611d90565b61261c565b82525050565b5f61264782886125e5565b60148201915061265782876125e5565b6014820191506126678286612605565b60148201915061267782856125e5565b6014820191506126878284612625565b6020820191508190509695505050505050565b6126a381611de2565b82525050565b6126b281611d90565b82525050565b5f6040820190506126cb5f83018561269a565b6126d860208301846126a9565b9392505050565b5f815190506126ed81612045565b92915050565b5f6020828403121561270857612707611d59565b5b5f612715848285016126df565b91505092915050565b61272781611d5d565b82525050565b5f6040820190506127405f83018561271e565b61274d602083018461271e565b9392505050565b6004811061276557612764611f1b565b5b50565b5f81905061277582612754565b919050565b5f61278482612768565b9050919050565b5f8160f81b9050919050565b5f6127a18261278b565b9050919050565b6127b96127b48261277a565b612797565b82525050565b5f6127c982612797565b9050919050565b6127e16127dc8261203a565b6127bf565b82525050565b5f6127f2828b6125e5565b601482019150612802828a6125e5565b6014820191506128128289612605565b60148201915061282282886125e5565b6014820191506128328287612625565b60208201915061284282866127a8565b60018201915061285282856127d0565b6001820191506128628284612625565b6020820191508190509998505050505050505050565b5f60208201905061288b5f83018461271e565b92915050565b5f6060820190506128a45f83018661269a565b6128b1602083018561269a565b6128be60408301846126a9565b949350505050565b5f82825260208201905092915050565b7f5061796d656e7420776173206e6f742073656e740000000000000000000000005f82015250565b5f61290a6014836128c6565b9150612915826128d6565b602082019050919050565b5f6020820190508181035f830152612937816128fe565b9050919050565b7f496e76616c6964207061796d656e7420686173680000000000000000000000005f82015250565b5f6129726014836128c6565b915061297d8261293e565b602082019050919050565b5f6020820190508181035f83015261299f81612966565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6129dd82611d90565b91506129e883611d90565b9250828203905081811115612a00576129ff6129a6565b5b92915050565b7f546f6b656e207472616e73666572206661696c656400000000000000000000005f82015250565b5f612a3a6015836128c6565b9150612a4582612a06565b602082019050919050565b5f6020820190508181035f830152612a6781612a2e565b905091905056fea26469706673582212203106867e1b147b377237cde0aba42d82faf0282b83d7b6d62cca039d0b7f840564736f6c63430008160033 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/dummy_files/iris-ibc-nucleus-history.json b/mm2src/mm2_test_helpers/dummy_files/iris-ibc-nucleus-history.json new file mode 100644 index 0000000000..48049cfef4 --- /dev/null +++ b/mm2src/mm2_test_helpers/dummy_files/iris-ibc-nucleus-history.json @@ -0,0 +1,30 @@ +[ + { + "tx_hex": "0a2a6e7563317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c37376e303738122a6e756331366e6c653735746572323674613278303671726d6e7066307833786637346e337276763677391a4f0a446962632f46374632384646334330393032344130323235454442424442323037453538373244324234454632464238373446453437423035454639433941374432313143120734303030303030", + "tx_hash": "9EB45C82244FEF0A1D84AF59780389A265B29D18EAC7B7FA0DEE8F5E3B9522E7", + "from": [ + "nuc1z609g9z8ef4jlkcfgg36c2ljgcmna50l77n078" + ], + "to": [ + "nuc16nle75ter26ta2x06qrmnpf0x3xf74n3rvv6w9" + ], + "total_amount": "4", + "spent_by_me": "4", + "received_by_me": "0", + "my_balance_change": "-4", + "block_height": 178, + "timestamp": 1717499824, + "fee_details": { + "type": "Tendermint", + "coin": "NUCLEUS-TEST", + "amount": "0.025689", + "gas_limit": 125000 + }, + "coin": "IRIS-IBC-NUCLEUS-TEST", + "internal_id": "3935464134384431413046454634343232384335344245390000000000000000", + "transaction_type": { + "TokenTransfer": "da5368780890dadaff39ad08ae29eaf6ae408fc4f8ece2f1336975ec2e70095f" + }, + "memo": "" + } +] \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/dummy_files/iris_nimda_history.json b/mm2src/mm2_test_helpers/dummy_files/iris_nimda_history.json deleted file mode 100644 index e75d8aaf5a..0000000000 --- a/mm2src/mm2_test_helpers/dummy_files/iris_nimda_history.json +++ /dev/null @@ -1,286 +0,0 @@ -[ - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a726130122a696161316530727838376d646a37397a656a65777563346a6737716c39756432323836673275733866322a0c0a036e696d1205313030303032403838613735653161306166373662636161386266353566313437616266616262313362323964646333316465323161373431363835333966373135643032343240980c", - "tx_hash": "B06D02A1449B07E89D15E709C347E0EE72A944687942E5A10467EC096D067FE7", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.01", - "spent_by_me": "0.01", - "received_by_me": "0", - "my_balance_change": "-0.01", - "block_height": 6175147, - "timestamp": 1669934633, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.027385", - "gas_limit": 125000 - }, - "coin": "IRIS-NIMDA", - "internal_id": "3930374535314439384537304239343431413230443630420000000000000000", - "transaction_type": { - "CustomTendermintMsg": { - "msg_type": "SendHtlcAmount", - "token_id": "e13c266c61de5e79cf37a99b94d4d4462d6857fe8a5dbdde9e041ae01b14a7db" - } - }, - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a726130122a696161316530727838376d646a37397a656a65777563346a6737716c39756432323836673275733866322a0c0a036e696d1205313030303032406466313465616234366366623230663736613465336233393537656234326137666130646435366137386466623038656134323034666334383762663536323040980c", - "tx_hash": "26E7CA4A95C70316195E8C8A3249351710B1BFE89AE9B910463BC8280C0AA9CF", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.01", - "spent_by_me": "0.01", - "received_by_me": "0", - "my_balance_change": "-0.01", - "block_height": 6175115, - "timestamp": 1669934472, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.028433", - "gas_limit": 125000 - }, - "coin": "IRIS-NIMDA", - "internal_id": "4138433845353931363133303743353941344143374536320000000000000000", - "transaction_type": { - "CustomTendermintMsg": { - "msg_type": "SendHtlcAmount", - "token_id": "e13c266c61de5e79cf37a99b94d4d4462d6857fe8a5dbdde9e041ae01b14a7db" - } - }, - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a7261301240364636394643303235393632413032463736363741414142393932313245393434454244444436434143314235354431443336333139383341413433454145421a4030646432663130353032343463386235316363636239316535363734386336373032343634393263383465323835656234353365363166373630623730356432", - "tx_hash": "459F88CACCD57756DDFE2164AABBA2C3D463D0FBA5D85DFD79488416A9BD54FC", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.01", - "spent_by_me": "0", - "received_by_me": "0.01", - "my_balance_change": "0.01", - "block_height": 6175070, - "timestamp": 1669934246, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.026403", - "gas_limit": 125000 - }, - "coin": "IRIS-NIMDA", - "internal_id": "3436313245464444363537373544434341433838463935340000000000000000", - "transaction_type": { - "CustomTendermintMsg": { - "msg_type": "SignClaimHtlc", - "token_id": "e13c266c61de5e79cf37a99b94d4d4462d6857fe8a5dbdde9e041ae01b14a7db" - } - }, - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a726130122a696161316530727838376d646a37397a656a65777563346a6737716c39756432323836673275733866322a0c0a036e696d1205313030303032403738646661636564613462346362373336366362626638643436336237393061666566363566656538613764356464356264616633613365303766343330623640980c", - "tx_hash": "25D9A1F1C9383AC7D56E3E19F3D6B9ED3F52F7D0E9225ABBEA0258FF75E28FC2", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.01", - "spent_by_me": "0.01", - "received_by_me": "0", - "my_balance_change": "-0.01", - "block_height": 6148309, - "timestamp": 1669798875, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.028433", - "gas_limit": 125000 - }, - "coin": "IRIS-NIMDA", - "internal_id": "3931453345363544374341333833394331463141394435320000000000000000", - "transaction_type": { - "CustomTendermintMsg": { - "msg_type": "SendHtlcAmount", - "token_id": "e13c266c61de5e79cf37a99b94d4d4462d6857fe8a5dbdde9e041ae01b14a7db" - } - }, - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a726130122a696161316567307167617a37336a737676727676747a713478383233686d7a387161706c64643078347a1a0a0a036e696d1203313030", - "tx_hash": "D83D8143CAD8C225F3BC24C6DCA324DB2F63C97A21AE2C030328266E9415E50A", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [ - "iaa1eg0qgaz73jsvvrvvtzq4x823hmz8qapldd0x4z" - ], - "total_amount": "0.0001", - "spent_by_me": "0.0001", - "received_by_me": "0", - "my_balance_change": "-0.0001", - "block_height": 6148307, - "timestamp": 1669798865, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.025763", - "gas_limit": 125000 - }, - "coin": "IRIS-NIMDA", - "internal_id": "3643343243423346353232433844414333343138443338440000000000000000", - "transaction_type": { - "TokenTransfer": "e13c266c61de5e79cf37a99b94d4d4462d6857fe8a5dbdde9e041ae01b14a7db" - }, - "memo": "2dee652b-6892-46a9-b54a-b3333c6ce7f6" - }, - { - "tx_hex": "0a2a696161316530727838376d646a37397a656a65777563346a6737716c3975643232383667327573386632122a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a7261301a0e0a036e696d120731303030303030", - "tx_hash": "7A43C30967D30B12B749111F217A670C0B47B7C5F272BCD82C442C1554351CB7", - "from": [ - "iaa1e0rx87mdj79zejewuc4jg7ql9ud2286g2us8f2" - ], - "to": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "total_amount": "2", - "spent_by_me": "0", - "received_by_me": "2", - "my_balance_change": "2", - "block_height": 6148055, - "timestamp": 1669797598, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.022114", - "gas_limit": 125000 - }, - "coin": "IRIS-NIMDA", - "internal_id": "4631313139343742323142303344373639303343333441370000000000000000", - "transaction_type": { - "TokenTransfer": "e13c266c61de5e79cf37a99b94d4d4462d6857fe8a5dbdde9e041ae01b14a7db" - }, - "memo": "" - }, - { - "tx_hex": "0a2a696161316530727838376d646a37397a656a65777563346a6737716c3975643232383667327573386632122a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a7261301a0e0a036e696d1207313030303030301a100a05756e79616e120731303030303030", - "tx_hash": "3057F075161ECD149F4F5A629F852D5E7E57411DECE7AD35CC7D11E7EB06E0BF", - "from": [ - "iaa1e0rx87mdj79zejewuc4jg7ql9ud2286g2us8f2" - ], - "to": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "total_amount": "2", - "spent_by_me": "0", - "received_by_me": "2", - "my_balance_change": "2", - "block_height": 6148039, - "timestamp": 1669797517, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.026861", - "gas_limit": 125000 - }, - "coin": "IRIS-NIMDA", - "internal_id": "3236413546344639343144434531363135373046373530330000000000000000", - "transaction_type": { - "TokenTransfer": "e13c266c61de5e79cf37a99b94d4d4462d6857fe8a5dbdde9e041ae01b14a7db" - }, - "memo": "" - }, - { - "tx_hex": "0a2a696161316530727838376d646a37397a656a65777563346a6737716c3975643232383667327573386632122a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a7261301a0e0a036e696d1207313030303030301a100a05756e79616e120731303030303030", - "tx_hash": "14FA04603223A5753DD60ADCE2593FD4AFA0A386E008AF7130916152F11D5748", - "from": [ - "iaa1e0rx87mdj79zejewuc4jg7ql9ud2286g2us8f2" - ], - "to": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "total_amount": "1", - "spent_by_me": "0", - "received_by_me": "1", - "my_balance_change": "1", - "block_height": 6148028, - "timestamp": 1669797462, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.026861", - "gas_limit": 125000 - }, - "coin": "IRIS-NIMDA", - "internal_id": "4344413036444433353735413332323330363430414634310000000000000000", - "transaction_type": { - "TokenTransfer": "e13c266c61de5e79cf37a99b94d4d4462d6857fe8a5dbdde9e041ae01b14a7db" - }, - "memo": "" - }, - { - "tx_hex": "0a2a696161316530727838376d646a37397a656a65777563346a6737716c3975643232383667327573386632122a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a7261301a0e0a036e696d120731303030303030", - "tx_hash": "3DC1E9372E873C69E009B89C3DE232E9E12D0FC941BB98582B729A5D64DB6308", - "from": [ - "iaa1e0rx87mdj79zejewuc4jg7ql9ud2286g2us8f2" - ], - "to": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "total_amount": "1", - "spent_by_me": "0", - "received_by_me": "1", - "my_balance_change": "1", - "block_height": 6148010, - "timestamp": 1669797371, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.022114", - "gas_limit": 125000 - }, - "coin": "IRIS-NIMDA", - "internal_id": "4339384239303045393643333738453237333945314344330000000000000000", - "transaction_type": { - "TokenTransfer": "e13c266c61de5e79cf37a99b94d4d4462d6857fe8a5dbdde9e041ae01b14a7db" - }, - "memo": "" - }, - { - "tx_hex": "0a2a696161316530727838376d646a37397a656a65777563346a6737716c3975643232383667327573386632122a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a7261301a0e0a036e696d120731303030303030", - "tx_hash": "E75EF2C47E8627B18840606A8F3502D5643984334B2FB5D8C69FF3D1F2B48473", - "from": [ - "iaa1e0rx87mdj79zejewuc4jg7ql9ud2286g2us8f2" - ], - "to": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "total_amount": "1", - "spent_by_me": "0", - "received_by_me": "1", - "my_balance_change": "1", - "block_height": 6147988, - "timestamp": 1669797261, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.02316", - "gas_limit": 125000 - }, - "coin": "IRIS-NIMDA", - "internal_id": "4136303630343838314237323638453734433246453537450000000000000000", - "transaction_type": { - "TokenTransfer": "e13c266c61de5e79cf37a99b94d4d4462d6857fe8a5dbdde9e041ae01b14a7db" - }, - "memo": "" - } -] \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/dummy_files/iris_test_history.json b/mm2src/mm2_test_helpers/dummy_files/iris_test_history.json deleted file mode 100644 index a5cc44de43..0000000000 --- a/mm2src/mm2_test_helpers/dummy_files/iris_test_history.json +++ /dev/null @@ -1,426 +0,0 @@ -[ - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a726130122a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a7261301a0f0a05756e79616e1206313030303030", - "tx_hash": "FA10C0D7C13F4D7A08DEB88368EB8DE3A0610EB42B2A44B7BF17D9C17ABF7DCF", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "total_amount": "0.122145", - "spent_by_me": "0.122145", - "received_by_me": "0.1", - "my_balance_change": "-0.022145", - "block_height": 6649830, - "timestamp": 1672328326, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.022145", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "3338384245443830413744344633314337443043303141460000000000000000", - "transaction_type": "StandardTransfer", - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a7261301240353136384331423544333836313243373835414530364141344130363036443537433444454343373939333834314338344544313932363138303941333335361a4064323032323032336639656565633338393561326637323063383463613363383638653632656232393139666262613732376262333464623434313739333232", - "tx_hash": "072AB56BE582BB1EF82193D6EDF855A0311E53D9240EBEEE5E7844898C54DADD", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.037215", - "spent_by_me": "0.027215", - "received_by_me": "0.01", - "my_balance_change": "-0.017215", - "block_height": 6175150, - "timestamp": 1669934648, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.027215", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "3644333931323846453142423238354542363542413237300000000000000000", - "transaction_type": { - "CustomTendermintMsg": { - "msg_type": "SignClaimHtlc", - "token_id": null - } - }, - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a726130122a696161316530727838376d646a37397a656a65777563346a6737716c39756432323836673275733866322a0c0a036e696d1205313030303032403838613735653161306166373662636161386266353566313437616266616262313362323964646333316465323161373431363835333966373135643032343240980c", - "tx_hash": "B06D02A1449B07E89D15E709C347E0EE72A944687942E5A10467EC096D067FE7", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.027385", - "spent_by_me": "0.027385", - "received_by_me": "0", - "my_balance_change": "-0.027385", - "block_height": 6175147, - "timestamp": 1669934633, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.027385", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "0000000000000000423036443032413134343942303745383944313545373039", - "transaction_type": "FeeForTokenTx", - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a726130122a696161316530727838376d646a37397a656a65777563346a6737716c39756432323836673275733866322a0c0a036e696d1205313030303032406466313465616234366366623230663736613465336233393537656234326137666130646435366137386466623038656134323034666334383762663536323040980c", - "tx_hash": "26E7CA4A95C70316195E8C8A3249351710B1BFE89AE9B910463BC8280C0AA9CF", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.028433", - "spent_by_me": "0.028433", - "received_by_me": "0", - "my_balance_change": "-0.028433", - "block_height": 6175115, - "timestamp": 1669934472, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.028433", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "0000000000000000323645374341344139354337303331363139354538433841", - "transaction_type": "FeeForTokenTx", - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a7261301240364636394643303235393632413032463736363741414142393932313245393434454244444436434143314235354431443336333139383341413433454145421a4030646432663130353032343463386235316363636239316535363734386336373032343634393263383465323835656234353365363166373630623730356432", - "tx_hash": "459F88CACCD57756DDFE2164AABBA2C3D463D0FBA5D85DFD79488416A9BD54FC", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.026403", - "spent_by_me": "0.026403", - "received_by_me": "0", - "my_balance_change": "-0.026403", - "block_height": 6175070, - "timestamp": 1669934246, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.026403", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "0000000000000000343539463838434143434435373735364444464532313634", - "transaction_type": "FeeForTokenTx", - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a726130122a696161316530727838376d646a37397a656a65777563346a6737716c39756432323836673275733866322a0e0a05756e79616e1205313030303032403835306338333863393930363661323466373330363866623537336638356337353836323036613733366330353331323030396233323437313834643130656340980c", - "tx_hash": "8A4BD1D952AC833C4C11313580CAC61374C80D1641D6EBD459DE73AFA7DF47F2", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.03856", - "spent_by_me": "0.03856", - "received_by_me": "0", - "my_balance_change": "-0.03856", - "block_height": 6175068, - "timestamp": 1669934236, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.02856", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "3533313331314334433333384341323539443144423441380000000000000000", - "transaction_type": { - "CustomTendermintMsg": { - "msg_type": "SendHtlcAmount", - "token_id": null - } - }, - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a726130122a696161316567307167617a37336a737676727676747a713478383233686d7a387161706c64643078347a1a0c0a05756e79616e1203313030", - "tx_hash": "734623F6744A44ABA5D551233B409400CF69EC8858302E9AE868A8400E925D51", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [ - "iaa1eg0qgaz73jsvvrvvtzq4x823hmz8qapldd0x4z" - ], - "total_amount": "0.022185", - "spent_by_me": "0.022185", - "received_by_me": "0", - "my_balance_change": "-0.022185", - "block_height": 6175065, - "timestamp": 1669934221, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.022085", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "3332313535443541424134344134343736463332363433370000000000000000", - "transaction_type": "StandardTransfer", - "memo": "505b8d6e-43db-46b7-96d3-5150e7ac2036" - }, - { - "tx_hex": "0a2a696161316530727838376d646a37397a656a65777563346a6737716c39756432323836673275733866321240413231314538373937463739314534434439333241423539424239333545324138354644453843393638374143463644383345373933424545373438463936431a4037626266353433313865643235643530366131393565363432636433343736613835303438393833623030346334653534336663313938313866333130353237", - "tx_hash": "252D31A8AFEF8B4C8D9058E9EFAF4ED5A41DB7C032B3849A87375F1DE2B8B9C1", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.000001", - "spent_by_me": "0", - "received_by_me": "0.000001", - "my_balance_change": "0.000001", - "block_height": 6173346, - "timestamp": 1669925588, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.026446", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "3945383530394438433442384645464138413133443235320000000000000000", - "transaction_type": { - "CustomTendermintMsg": { - "msg_type": "ClaimHtlcAmount", - "token_id": null - } - }, - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a7261301240384636304534433336384335344539343233344145424135394331344531384146413138443437303530314434383944444641364539363635444135323738451a4064363034613132626531616239643966343531306435343832666232323762613530363238373531326466333561626337303730313763366439306165303837", - "tx_hash": "6BA67D4C7A0F48E5D85EE18EF5A649ED456B539D902828D56849B57DCFF5914B", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.026426", - "spent_by_me": "0.026426", - "received_by_me": "0", - "my_balance_change": "-0.026426", - "block_height": 6148319, - "timestamp": 1669798925, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.026426", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "4538314545353844354538344630413743344437364142360000000000000000", - "transaction_type": { - "CustomTendermintMsg": { - "msg_type": "SignClaimHtlc", - "token_id": null - } - }, - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a726130122a696161316572666e6b6a736d616c6b7774766a3434716e6672326472667a6474346e396c6468306b6a762a0a0a05756e79616e12013132403834373331663565393739643965616263343838393234356131613165663932396430386437383832353163353133633834303632373535313032343765326540e807", - "tx_hash": "F1C633D16C1578A16450555A06349989A4B1E2223D7AAA2FE12396A5F855FD9B", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.028456", - "spent_by_me": "0.028456", - "received_by_me": "0", - "my_balance_change": "-0.028456", - "block_height": 6148318, - "timestamp": 1669798920, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.028455", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "4135353530353436314138373531433631443333364331460000000000000000", - "transaction_type": { - "CustomTendermintMsg": { - "msg_type": "SendHtlcAmount", - "token_id": null - } - }, - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a7261301240304544424136304632423642383739324133333739423232453445323144373243343145413530464436353331433243383445413135353937303834373432391a4034353138383334373164316536383461323334663362323063626638646137393338636330643038383562626237326266303837363465386431356536353138", - "tx_hash": "10D92256317083E49F06A9DD2184714DEEE9C55A505CFEF037BE8E87E9FB82ED", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.03648", - "spent_by_me": "0.02648", - "received_by_me": "0.01", - "my_balance_change": "-0.01648", - "block_height": 6148311, - "timestamp": 1669798885, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.02648", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "4444394136304639344533383037313336353232394430310000000000000000", - "transaction_type": { - "CustomTendermintMsg": { - "msg_type": "SignClaimHtlc", - "token_id": null - } - }, - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a726130122a696161316530727838376d646a37397a656a65777563346a6737716c39756432323836673275733866322a0c0a036e696d1205313030303032403738646661636564613462346362373336366362626638643436336237393061666566363566656538613764356464356264616633613365303766343330623640980c", - "tx_hash": "25D9A1F1C9383AC7D56E3E19F3D6B9ED3F52F7D0E9225ABBEA0258FF75E28FC2", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.028433", - "spent_by_me": "0.028433", - "received_by_me": "0", - "my_balance_change": "-0.028433", - "block_height": 6148309, - "timestamp": 1669798875, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.028433", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "0000000000000000323544394131463143393338334143374435364533453139", - "transaction_type": "FeeForTokenTx", - "memo": "" - }, - { - "tx_hex": "0a2a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a726130122a696161316567307167617a37336a737676727676747a713478383233686d7a387161706c64643078347a1a0a0a036e696d1203313030", - "tx_hash": "D83D8143CAD8C225F3BC24C6DCA324DB2F63C97A21AE2C030328266E9415E50A", - "from": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "to": [], - "total_amount": "0.025763", - "spent_by_me": "0.025763", - "received_by_me": "0", - "my_balance_change": "-0.025763", - "block_height": 6148307, - "timestamp": 1669798865, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.025763", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "0000000000000000443833443831343343414438433232354633424332344336", - "transaction_type": "FeeForTokenTx", - "memo": "2dee652b-6892-46a9-b54a-b3333c6ce7f6" - }, - { - "tx_hex": "0a2a696161316530727838376d646a37397a656a65777563346a6737716c3975643232383667327573386632122a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a7261301a0e0a036e696d1207313030303030301a100a05756e79616e120731303030303030", - "tx_hash": "3057F075161ECD149F4F5A629F852D5E7E57411DECE7AD35CC7D11E7EB06E0BF", - "from": [ - "iaa1e0rx87mdj79zejewuc4jg7ql9ud2286g2us8f2" - ], - "to": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "total_amount": "2", - "spent_by_me": "0", - "received_by_me": "2", - "my_balance_change": "2", - "block_height": 6148039, - "timestamp": 1669797517, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.026861", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "3236413546344639343144434531363135373046373530330000000000000001", - "transaction_type": "StandardTransfer", - "memo": "" - }, - { - "tx_hex": "0a2a696161316530727838376d646a37397a656a65777563346a6737716c3975643232383667327573386632122a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a7261301a0e0a036e696d1207313030303030301a100a05756e79616e120731303030303030", - "tx_hash": "14FA04603223A5753DD60ADCE2593FD4AFA0A386E008AF7130916152F11D5748", - "from": [ - "iaa1e0rx87mdj79zejewuc4jg7ql9ud2286g2us8f2" - ], - "to": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "total_amount": "1", - "spent_by_me": "0", - "received_by_me": "1", - "my_balance_change": "1", - "block_height": 6148028, - "timestamp": 1669797462, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.026861", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "4344413036444433353735413332323330363430414634310000000000000001", - "transaction_type": "StandardTransfer", - "memo": "" - }, - { - "tx_hex": "0a2a696161317039703230667468306c7665647634736d7733327339377079386e74657230716e7774727538122a696161317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c32796a7261301a120a05756e79616e1209313030303030303030", - "tx_hash": "AEABA70DB11EAE7D4C47E9F892C99280EF64E0DA58E1B0286DC8A190166A1218", - "from": [ - "iaa1p9p20fth0lvedv4smw32s97py8nter0qnwtru8" - ], - "to": [ - "iaa1z609g9z8ef4jlkcfgg36c2ljgcmna50l2yjra0" - ], - "total_amount": "100", - "spent_by_me": "0", - "received_by_me": "100", - "my_balance_change": "100", - "block_height": 6147959, - "timestamp": 1669797114, - "fee_details": { - "type": "Tendermint", - "coin": "IRIS-TEST", - "amount": "0.2", - "gas_limit": 125000 - }, - "coin": "IRIS-TEST", - "internal_id": "3846394537344334443745414531314244303741424145410000000000000000", - "transaction_type": "StandardTransfer", - "memo": "test" - } -] \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/dummy_files/nucleus-history.json b/mm2src/mm2_test_helpers/dummy_files/nucleus-history.json new file mode 100644 index 0000000000..4400c318e0 --- /dev/null +++ b/mm2src/mm2_test_helpers/dummy_files/nucleus-history.json @@ -0,0 +1,249 @@ +[ + { + "tx_hex": "0a0f30372d74656e6465726d696e742d321288070a262f6962632e6c69676874636c69656e74732e74656e6465726d696e742e76312e48656164657212dd060ad5040a98030a02080b1211636f736d6f736875622d746573746e657418df06220c08efe4c2b80610b7c1ffe8022a480a201b8fdfca293c20e10c86c89ce5a66cedd40cbf83f3833e504844fb89f6606d0b122408011220e58f2ab7b93c559d54eb0f62111fd5c065121ef09e93be370a523af1620f2c3732201692b08be7826c81b282e7f1ef95b75a3e59e824827fab5fe3419b40ba4b7e2f3a20cf0ff3ec944444eef5c483642c33019c7f1c6dd168bcb0cf70dc4a7606edb94b42209293a1e6e3030b68610f5d4eaff415483ba6d7d493b6d86ea22cb25bea4258bb4a209293a1e6e3030b68610f5d4eaff415483ba6d7d493b6d86ea22cb25bea4258bb5220048091bc7ddc283f77bfbf91d73c44da58c3df8a9cbc867405d8b7f3daada22f5a205dc49011b034d2315b08ec147193cf45dadd9133de1f4aaeea2098068b9c9d256220e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8556a20e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85572143e49b197ac395db2bd999b4c3f35f1b6b36508d012b70108df061a480a207348ea274d50794629bb1e196d0280dfe01588687ae576f320f2e0e4c4be897c122408011220a01fbeb8a9a603cc741f7a44f050e0c9694adba7772738e6f7599019f6f7f1072268080212143e49b197ac395db2bd999b4c3f35f1b6b36508d01a0c08f4e4c2b8061094d8feeb022240bf027c9e216ed59b699a3e09ba5e09397ed401e025690463b3fce0421c917b144c335b544e994482a2160a1fb10fd7dd10b8dff5bb24e625652f3187c0feb90d127e0a3d0a143e49b197ac395db2bd999b4c3f35f1b6b36508d012220a208c9bbd034ed5ccfeb619b9588266c11de77c0e59853558368c468cd29e852d0518c801123d0a143e49b197ac395db2bd999b4c3f35f1b6b36508d012220a208c9bbd034ed5ccfeb619b9588266c11de77c0e59853558368c468cd29e852d0518c8011a03109c06227e0a3d0a143e49b197ac395db2bd999b4c3f35f1b6b36508d012220a208c9bbd034ed5ccfeb619b9588266c11de77c0e59853558368c468cd29e852d0518c801123d0a143e49b197ac395db2bd999b4c3f35f1b6b36508d012220a208c9bbd034ed5ccfeb619b9588266c11de77c0e59853558368c468cd29e852d0518c8011a2a6e7563313232726b6e3578306b677a72787a7964797966766c6e6e653433716a387578393866677a7470", + "tx_hash": "BF87EAB193BC8E1C8975105C165497DDC4AE4D5378F50AECBA38D43B715D96F3", + "from": [ + "nuc12k2pyuylm9t7ugdvz67h9pg4gmmvhn5v77y0sd" + ], + "to": [ + "nuc1z609g9z8ef4jlkcfgg36c2ljgcmna50l77n078" + ], + "total_amount": "0.615", + "spent_by_me": "0", + "received_by_me": "0.615", + "my_balance_change": "0.615", + "block_height": 1183, + "timestamp": 1729147508, + "fee_details": { + "type": "Tendermint", + "coin": "NUCLEUS-TEST", + "amount": "0.12201", + "gas_limit": 125000 + }, + "coin": "NUCLEUS-TEST", + "internal_id": "4335303135373938433145384342333931424145373846420000000000000000", + "transaction_type": "StandardTransfer", + "memo": "rly(2.5.2-28-gdf42391)" + }, + { + "tx_hex": "0a087472616e7366657212096368616e6e656c2d321a0f0a05756e75636c1206363135303030222a6e7563317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c37376e3037382a2d636f736d6f7331333468397476373836366a63757737303877357737366c6366783773337832797379616c787938f3edc3aadad1c9ff17", + "tx_hash": "95BA748DC10BE865C1EBEA10A433E8E5F626E14474676037374539C6FBC74655", + "from": [ + "nuc1z609g9z8ef4jlkcfgg36c2ljgcmna50l77n078" + ], + "to": [ + "cosmos134h9tv7866jcuw708w5w76lcfx7s3x2ysyalxy" + ], + "total_amount": "0.651435", + "spent_by_me": "0.651435", + "received_by_me": "0", + "my_balance_change": "-0.651435", + "block_height": 1128, + "timestamp": 1729142296, + "fee_details": { + "type": "Tendermint", + "coin": "NUCLEUS-TEST", + "amount": "0.036435", + "gas_limit": 125000 + }, + "coin": "NUCLEUS-TEST", + "internal_id": "3031414542453143353638454230314344383437414235390000000000000000", + "transaction_type": "StandardTransfer", + "memo": "" + }, + { + "tx_hex": "0a2a6e7563317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c37376e3037381240303932443741333142373239304433434438384231414338344336433338313137344134444638393046323831343232343335373334413145323534344641461a4039393836333361343532623636373561663632626261666364646630376461336264343633343262623935373935643637346335373162613363666661386433", + "tx_hash": "17C70B8D8B4DA83F5C625A32DAD2BD2572F0770D6F7AD52284781E71C20F89E4", + "from": [ + "nuc1z609g9z8ef4jlkcfgg36c2ljgcmna50l77n078" + ], + "to": [], + "total_amount": "0.027007", + "spent_by_me": "0.027007", + "received_by_me": "0", + "my_balance_change": "-0.027007", + "block_height": 202, + "timestamp": 1717499945, + "fee_details": { + "type": "Tendermint", + "coin": "NUCLEUS-TEST", + "amount": "0.027007", + "gas_limit": 125000 + }, + "coin": "NUCLEUS-TEST", + "internal_id": "3233413532364335463338414434423844384230374337310000000000000000", + "transaction_type": { + "CustomTendermintMsg": { + "msg_type": "SignClaimHtlc", + "token_id": null + } + }, + "memo": "" + }, + { + "tx_hex": "0a2a6e7563317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c37376e303738122a6e756331366e6c653735746572323674613278303671726d6e7066307833786637346e337276763677391a0a0a05756e75636c12013122403564663564643861313465623433323937396138636665393735393932626638656431393137613737643538663036623566623061633936353336316236333830e807", + "tx_hash": "32143439E0F876ED3B82A6DEDCD1F86DD756AF0C1BE9BED33A680DD52A380733", + "from": [ + "nuc1z609g9z8ef4jlkcfgg36c2ljgcmna50l77n078" + ], + "to": [], + "total_amount": "0.027345", + "spent_by_me": "0.027345", + "received_by_me": "0", + "my_balance_change": "-0.027345", + "block_height": 201, + "timestamp": 1717499940, + "fee_details": { + "type": "Tendermint", + "coin": "NUCLEUS-TEST", + "amount": "0.027344", + "gas_limit": 125000 + }, + "coin": "NUCLEUS-TEST", + "internal_id": "4544364132384233444536373846304539333433343132330000000000000000", + "transaction_type": { + "CustomTendermintMsg": { + "msg_type": "SendHtlcAmount", + "token_id": null + } + }, + "memo": "" + }, + { + "tx_hex": "0a2a6e7563317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c37376e3037381240363238384239423446334531433143333741374345303833453443344133423537323335384135303746383835363044314341443343303937413431413946361a4033343939643665666664313835643062613634386264633635376239303362353737386661353832626630633837383264663930633466653833376564666161", + "tx_hash": "A871259AC1DCA12208C63C8824555DE58739F6BB949F50E632A45488393994E3", + "from": [ + "nuc1z609g9z8ef4jlkcfgg36c2ljgcmna50l77n078" + ], + "to": [], + "total_amount": "0.027007", + "spent_by_me": "0.027007", + "received_by_me": "0", + "my_balance_change": "-0.027007", + "block_height": 199, + "timestamp": 1717499930, + "fee_details": { + "type": "Tendermint", + "coin": "NUCLEUS-TEST", + "amount": "0.027007", + "gas_limit": 125000 + }, + "coin": "NUCLEUS-TEST", + "internal_id": "3838433336433830323231414344314341393532313738410000000000000000", + "transaction_type": { + "CustomTendermintMsg": { + "msg_type": "SignClaimHtlc", + "token_id": null + } + }, + "memo": "" + }, + { + "tx_hex": "0a2a6e7563317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c37376e303738122a6e756331366e6c653735746572323674613278303671726d6e7066307833786637346e337276763677391a0a0a05756e75636c12013122406531663066343833366331636364643762316230363836613761633766306261616139303835643262326234353035643434373736626335663431326165343430e807", + "tx_hash": "7964A8736BBB1447E203840FAFC76A7924100A03AF3DD2C65FFD654DA576CF20", + "from": [ + "nuc1z609g9z8ef4jlkcfgg36c2ljgcmna50l77n078" + ], + "to": [], + "total_amount": "0.027345", + "spent_by_me": "0.027345", + "received_by_me": "0", + "my_balance_change": "-0.027345", + "block_height": 198, + "timestamp": 1717499925, + "fee_details": { + "type": "Tendermint", + "coin": "NUCLEUS-TEST", + "amount": "0.027344", + "gas_limit": 125000 + }, + "coin": "NUCLEUS-TEST", + "internal_id": "4630343833303245373434314242423633373841343639370000000000000000", + "transaction_type": { + "CustomTendermintMsg": { + "msg_type": "SendHtlcAmount", + "token_id": null + } + }, + "memo": "" + }, + { + "tx_hex": "0a2a6e7563317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c37376e303738122a6e756331366e6c653735746572323674613278303671726d6e7066307833786637346e337276763677391a4f0a446962632f46374632384646334330393032344130323235454442424442323037453538373244324234454632464238373446453437423035454639433941374432313143120734303030303030", + "tx_hash": "9EB45C82244FEF0A1D84AF59780389A265B29D18EAC7B7FA0DEE8F5E3B9522E7", + "from": [ + "nuc1z609g9z8ef4jlkcfgg36c2ljgcmna50l77n078" + ], + "to": [], + "total_amount": "0.025689", + "spent_by_me": "0.025689", + "received_by_me": "0", + "my_balance_change": "-0.025689", + "block_height": 178, + "timestamp": 1717499824, + "fee_details": { + "type": "Tendermint", + "coin": "NUCLEUS-TEST", + "amount": "0.025689", + "gas_limit": 125000 + }, + "coin": "NUCLEUS-TEST", + "internal_id": "0000000000000000394542343543383232343446454630413144383441463539", + "transaction_type": "FeeForTokenTx", + "memo": "" + }, + { + "tx_hex": "0a2a6e7563317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c37376e303738122a6e756331366e6c653735746572323674613278303671726d6e7066307833786637346e337276763677391a0a0a05756e75636c12013122403232323662323036373639393439663866333436323032333637376534613362626134343134613932373164343934633762393462353533643935646364616130e807", + "tx_hash": "3752E94F6CFDBF86E7F0C17AB9E2739326D2E4308309F9356D901C52F067C15D", + "from": [ + "nuc1z609g9z8ef4jlkcfgg36c2ljgcmna50l77n078" + ], + "to": [], + "total_amount": "0.033096", + "spent_by_me": "0.033096", + "received_by_me": "0", + "my_balance_change": "-0.033096", + "block_height": 157, + "timestamp": 1717499719, + "fee_details": { + "type": "Tendermint", + "coin": "NUCLEUS-TEST", + "amount": "0.033095", + "gas_limit": 125000 + }, + "coin": "NUCLEUS-TEST", + "internal_id": "4137314330463745363846424446433646343945323537330000000000000000", + "transaction_type": { + "CustomTendermintMsg": { + "msg_type": "SendHtlcAmount", + "token_id": null + } + }, + "memo": "" + }, + { + "tx_hex": "0a2a6e7563317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c37376e303738122a6e756331366e6c653735746572323674613278303671726d6e7066307833786637346e337276763677391a0f0a05756e75636c1206363130303030", + "tx_hash": "E298CCD25B0893E1E52C1EA80AAB25CC7F0EC7976448E5E6EF29257887EDA6D1", + "from": [ + "nuc1z609g9z8ef4jlkcfgg36c2ljgcmna50l77n078" + ], + "to": [ + "nuc16nle75ter26ta2x06qrmnpf0x3xf74n3rvv6w9" + ], + "total_amount": "0.64142", + "spent_by_me": "0.64142", + "received_by_me": "0", + "my_balance_change": "-0.64142", + "block_height": 94, + "timestamp": 1717499403, + "fee_details": { + "type": "Tendermint", + "coin": "NUCLEUS-TEST", + "amount": "0.03142", + "gas_limit": 125000 + }, + "coin": "NUCLEUS-TEST", + "internal_id": "3841453143323545314533393830423532444343383932450000000000000000", + "transaction_type": "StandardTransfer", + "memo": "" + } +] \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 4d6d74fd71..7592384696 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -81,7 +81,7 @@ pub const MAKER_ERROR_EVENTS: [&str; 15] = [ "MakerPaymentRefundFinished", ]; -pub const TAKER_SUCCESS_EVENTS: [&str; 11] = [ +pub const TAKER_SUCCESS_EVENTS: [&str; 12] = [ "Started", "Negotiated", "TakerFeeSent", @@ -92,10 +92,11 @@ pub const TAKER_SUCCESS_EVENTS: [&str; 11] = [ "TakerPaymentSent", "TakerPaymentSpent", "MakerPaymentSpent", + "MakerPaymentSpendConfirmed", "Finished", ]; -pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 13] = [ +pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 14] = [ "Started", "Negotiated", "TakerFeeSent", @@ -108,11 +109,12 @@ pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 13] = [ "TakerPaymentSpent", "MakerPaymentSpent", "MakerPaymentSpentByWatcher", + "MakerPaymentSpendConfirmed", "Finished", ]; // Taker using watchers and watcher spends maker payment -pub const TAKER_ACTUAL_EVENTS_WATCHER_SPENDS_MAKER_PAYMENT: [&str; 12] = [ +pub const TAKER_ACTUAL_EVENTS_WATCHER_SPENDS_MAKER_PAYMENT: [&str; 13] = [ "Started", "Negotiated", "TakerFeeSent", @@ -124,11 +126,12 @@ pub const TAKER_ACTUAL_EVENTS_WATCHER_SPENDS_MAKER_PAYMENT: [&str; 12] = [ "WatcherMessageSent", "TakerPaymentSpent", "MakerPaymentSpentByWatcher", + "MakerPaymentSpendConfirmed", "Finished", ]; // Taker using watchers and spends maker payment instead of watcher -pub const TAKER_ACTUAL_EVENTS_TAKER_SPENDS_MAKER_PAYMENT: [&str; 12] = [ +pub const TAKER_ACTUAL_EVENTS_TAKER_SPENDS_MAKER_PAYMENT: [&str; 13] = [ "Started", "Negotiated", "TakerFeeSent", @@ -140,10 +143,11 @@ pub const TAKER_ACTUAL_EVENTS_TAKER_SPENDS_MAKER_PAYMENT: [&str; 12] = [ "WatcherMessageSent", "TakerPaymentSpent", "MakerPaymentSpent", + "MakerPaymentSpendConfirmed", "Finished", ]; -pub const TAKER_ERROR_EVENTS: [&str; 16] = [ +pub const TAKER_ERROR_EVENTS: [&str; 17] = [ "StartFailed", "NegotiateFailed", "TakerFeeSendFailed", @@ -154,6 +158,7 @@ pub const TAKER_ERROR_EVENTS: [&str; 16] = [ "TakerPaymentDataSendFailed", "TakerPaymentWaitForSpendFailed", "MakerPaymentSpendFailed", + "MakerPaymentSpendConfirmFailed", "TakerPaymentWaitRefundStarted", "TakerPaymentRefundStarted", "TakerPaymentRefunded", @@ -236,7 +241,11 @@ pub const ETH_MAINNET_NODE: &str = "https://mainnet.infura.io/v3/c01c1b4cf666425 pub const ETH_MAINNET_CHAIN_ID: u64 = 1; pub const ETH_MAINNET_SWAP_CONTRACT: &str = "0x24abe4c71fc658c91313b6552cd40cd808b3ea80"; -pub const ETH_SEPOLIA_NODES: &[&str] = &["https://rpc2.sepolia.org"]; +pub const ETH_SEPOLIA_NODES: &[&str] = &[ + "https://ethereum-sepolia-rpc.publicnode.com", + "https://rpc2.sepolia.org", + "https://1rpc.io/sepolia", +]; pub const ETH_SEPOLIA_CHAIN_ID: u64 = 11155111; pub const ETH_SEPOLIA_SWAP_CONTRACT: &str = "0xeA6D65434A15377081495a9E7C5893543E7c32cB"; pub const ETH_SEPOLIA_TOKEN_CONTRACT: &str = "0x09d0d71FBC00D7CCF9CFf132f5E6825C88293F19"; @@ -310,6 +319,21 @@ impl Mm2TestConf { } } + pub fn seednode_with_wallet_name(coins: &Json, wallet_name: &str, wallet_password: &str) -> Self { + Mm2TestConf { + conf: json!({ + "gui": "nogui", + "netid": 9998, + "coins": coins, + "rpc_password": DEFAULT_RPC_PASSWORD, + "i_am_seed": true, + "wallet_name": wallet_name, + "wallet_password": wallet_password, + }), + rpc_password: DEFAULT_RPC_PASSWORD.into(), + } + } + pub fn light_node(passphrase: &str, coins: &Json, seednodes: &[&str]) -> Self { Mm2TestConf { conf: json!({ @@ -593,7 +617,7 @@ pub fn atom_testnet_conf() -> Json { "decimals": 6, "denom": "uatom", "account_prefix": "cosmos", - "chain_id": "theta-testnet-001", + "chain_id": "cosmoshub-testnet", }, }, "derivation_path": "m/44'/118'", @@ -828,6 +852,13 @@ pub fn erc20_dev_conf(contract_address: &str) -> Json { }) } +/// ERC20 token configuration used for dockerized tests on Sepolia +pub fn sepolia_erc20_dev_conf(contract_address: &str) -> Json { + let mut conf = erc20_dev_conf(contract_address); + set_chain_id(&mut conf, ETH_SEPOLIA_CHAIN_ID); + conf +} + /// global NFT configuration used for dockerized Geth dev node pub fn nft_dev_conf() -> Json { json!({ @@ -846,12 +877,14 @@ pub fn nft_dev_conf() -> Json { }) } +fn set_chain_id(conf: &mut Json, chain_id: u64) { conf["chain_id"] = json!(chain_id); } + pub fn eth_sepolia_conf() -> Json { json!({ "coin": "ETH", "name": "ethereum", "derivation_path": "m/44'/60'", - "chain_id": 11155111, + "chain_id": ETH_SEPOLIA_CHAIN_ID, "protocol": { "type": "ETH" }, @@ -865,7 +898,7 @@ pub fn eth_sepolia_trezor_firmware_compat_conf() -> Json { "coin": "tETH", "name": "ethereum", "derivation_path": "m/44'/1'", // Note: trezor uses coin type 1' for eth for testnet (SLIP44_TESTNET) - "chain_id": 11155111, + "chain_id": ETH_SEPOLIA_CHAIN_ID, "protocol": { "type": "ETH" }, @@ -895,12 +928,12 @@ pub fn jst_sepolia_conf() -> Json { json!({ "coin": "JST", "name": "jst", - "chain_id": 11155111, + "chain_id": ETH_SEPOLIA_CHAIN_ID, "protocol": { "type": "ERC20", "protocol_data": { "platform": "ETH", - "chain_id": 11155111, + "chain_id": ETH_SEPOLIA_CHAIN_ID, "contract_address": ETH_SEPOLIA_TOKEN_CONTRACT } }, @@ -912,14 +945,14 @@ pub fn jst_sepolia_trezor_conf() -> Json { json!({ "coin": "tJST", "name": "tjst", - "chain_id": 11155111, + "chain_id": ETH_SEPOLIA_CHAIN_ID, "derivation_path": "m/44'/1'", // Note: Trezor uses 1' coin type for all testnets "trezor_coin": "tETH", "protocol": { "type": "ERC20", "protocol_data": { "platform": "ETH", - "chain_id": 11155111, + "chain_id": ETH_SEPOLIA_CHAIN_ID, "contract_address": ETH_SEPOLIA_TOKEN_CONTRACT } } @@ -954,7 +987,7 @@ pub fn nucleus_testnet_conf() -> Json { "decimals": 6, "denom": "unucl", "account_prefix": "nuc", - "chain_id": "nucleus-3", + "chain_id": "nucleus-testnet", }, } }) @@ -1100,10 +1133,16 @@ pub fn mm_ctx_with_custom_db_with_conf(conf: Option) -> MmArc { let ctx = ctx_builder.into_mm_arc(); let connection = Connection::open_in_memory().unwrap(); - let _ = ctx.sqlite_connection.pin(Arc::new(Mutex::new(connection))); + let _ = ctx + .sqlite_connection + .set(Arc::new(Mutex::new(connection))) + .map_err(|_| "Already Initialized".to_string()); let connection = Connection::open_in_memory().unwrap(); - let _ = ctx.shared_sqlite_conn.pin(Arc::new(Mutex::new(connection))); + let _ = ctx + .shared_sqlite_conn + .set(Arc::new(Mutex::new(connection))) + .map_err(|_| "Already Initialized".to_string()); ctx } @@ -1117,7 +1156,10 @@ pub async fn mm_ctx_with_custom_async_db() -> MmArc { let ctx = MmCtxBuilder::new().into_mm_arc(); let connection = AsyncConnection::open_in_memory().await.unwrap(); - let _ = ctx.async_sqlite_connection.pin(Arc::new(AsyncMutex::new(connection))); + let _ = ctx + .async_sqlite_connection + .set(Arc::new(AsyncMutex::new(connection))) + .map_err(|_| "Already Initialized".to_string()); ctx } @@ -1340,17 +1382,49 @@ impl MarketMakerIt { /// Start a new MarketMaker locally. /// /// * `conf` - The command-line configuration passed to the MarketMaker. + /// * `userpass` - RPC API key. + /// * `local` - Function to start the MarketMaker locally. + /// * `envs` - The environment variables passed to the process. + /// The argument is ignored for nodes running in a browser. + #[cfg(target_arch = "wasm32")] + pub async fn start_with_envs( + conf: Json, + userpass: String, + local: Option, + _envs: &[(&str, &str)], + ) -> Result { + MarketMakerIt::start_market_maker(conf, userpass, local, None).await + } + + /// Start a new MarketMaker locally with a specific database namespace. + /// + /// * `conf` - The command-line configuration passed to the MarketMaker. + /// * `userpass` - RPC API key. + /// * `local` - Function to start the MarketMaker locally. + /// * `db_namespace_id` - The test database namespace identifier. + #[cfg(target_arch = "wasm32")] + pub async fn start_with_db( + conf: Json, + userpass: String, + local: Option, + db_namespace_id: u64, + ) -> Result { + MarketMakerIt::start_market_maker(conf, userpass, local, Some(db_namespace_id)).await + } + + /// Common helper function to start the MarketMaker. + /// + /// * `conf` - The command-line configuration passed to the MarketMaker. /// Unique P2P in-memory port is injected as `p2p_in_memory_port` unless this field is already present. /// * `userpass` - RPC API key. We should probably extract it automatically from the MM log. /// * `local` - Function to start the MarketMaker locally. Required for nodes running in a browser. - /// * `envs` - The enviroment variables passed to the process. - /// The argument is ignore for nodes running in a browser. + /// * `db_namespace_id` - Optional test database namespace identifier. #[cfg(target_arch = "wasm32")] - pub async fn start_with_envs( + async fn start_market_maker( mut conf: Json, userpass: String, local: Option, - _envs: &[(&str, &str)], + db_namespace_id: Option, ) -> Result { if conf["p2p_in_memory"].is_null() { conf["p2p_in_memory"] = Json::Bool(true); @@ -1366,10 +1440,18 @@ impl MarketMakerIt { conf["p2p_in_memory_port"] = Json::Number(new_p2p_port.into()); } - let ctx = mm2_core::mm_ctx::MmCtxBuilder::new() - .with_conf(conf.clone()) - .with_test_db_namespace() - .into_mm_arc(); + let ctx = { + let builder = MmCtxBuilder::new().with_conf(conf.clone()); + + let builder = if let Some(ns) = db_namespace_id { + builder.with_test_db_namespace_with_id(ns) + } else { + builder.with_test_db_namespace() + }; + + builder.into_mm_arc() + }; + let local = try_s!(local.ok_or("!local")); local(ctx.clone()); @@ -1452,7 +1534,7 @@ impl MarketMakerIt { let wasm_rpc = self .ctx .wasm_rpc - .as_option() + .get() .expect("'MmCtx::rpc' must be initialized already"); match wasm_rpc.request(payload.clone()).await { // Please note a new type of error will be introduced soon. @@ -1829,6 +1911,30 @@ pub async fn enable_qrc20( json::from_str(&electrum.1).unwrap() } +pub async fn peer_connection_healthcheck(mm: &MarketMakerIt, peer_address: &str) -> Json { + let response = mm + .rpc(&json!({ + "userpass": mm.userpass, + "method": "peer_connection_healthcheck", + "mmrpc": "2.0", + "params": { + "peer_address": peer_address + } + })) + .await + .unwrap(); + + assert_eq!( + response.0, + StatusCode::OK, + "RPC «peer_connection_healthcheck» failed with {} {}", + response.0, + response.1 + ); + + json::from_str(&response.1).unwrap() +} + /// Reads passphrase and userpass from .env file pub fn from_env_file(env: Vec) -> (Option, Option) { use regex::bytes::Regex; @@ -1935,22 +2041,6 @@ pub async fn enable_eth_coin( json::from_str(&enable.1).unwrap() } - -pub async fn enable_spl(mm: &MarketMakerIt, coin: &str) -> Json { - let req = json!({ - "userpass": mm.userpass, - "method": "enable_spl", - "mmrpc": "2.0", - "params": { - "ticker": coin, - "activation_params": {} - } - }); - let enable = mm.rpc(&req).await.unwrap(); - assert_eq!(enable.0, StatusCode::OK, "'enable_spl' failed: {}", enable.1); - json::from_str(&enable.1).unwrap() -} - pub async fn enable_slp(mm: &MarketMakerIt, coin: &str) -> Json { let enable = mm .rpc(&json!({ @@ -2048,38 +2138,6 @@ pub async fn enable_bch_with_tokens( json::from_str(&enable.1).unwrap() } -pub async fn enable_solana_with_tokens( - mm: &MarketMakerIt, - platform_coin: &str, - tokens: &[&str], - solana_client_url: &str, - tx_history: bool, -) -> Json { - let spl_requests: Vec<_> = tokens.iter().map(|ticker| json!({ "ticker": ticker })).collect(); - let req = json!({ - "userpass": mm.userpass, - "method": "enable_solana_with_tokens", - "mmrpc": "2.0", - "params": { - "ticker": platform_coin, - "confirmation_commitment": "finalized", - "allow_slp_unsafe_conf": true, - "client_url": solana_client_url, - "tx_history": tx_history, - "spl_tokens_requests": spl_requests, - } - }); - - let enable = mm.rpc(&req).await.unwrap(); - assert_eq!( - enable.0, - StatusCode::OK, - "'enable_bch_with_tokens' failed: {}", - enable.1 - ); - json::from_str(&enable.1).unwrap() -} - pub async fn my_tx_history_v2( mm: &MarketMakerIt, coin: &str, @@ -2828,6 +2886,20 @@ pub async fn get_shared_db_id(mm: &MarketMakerIt) -> GetSharedDbIdResult { res.result } +pub async fn get_wallet_names(mm: &MarketMakerIt) -> GetWalletNamesResult { + let request = mm + .rpc(&json!({ + "userpass": mm.userpass, + "method": "get_wallet_names", + "mmrpc": "2.0", + })) + .await + .unwrap(); + assert_eq!(request.0, StatusCode::OK, "'get_wallet_names' failed: {}", request.1); + let res: RpcSuccessResponse<_> = json::from_str(&request.1).unwrap(); + res.result +} + pub async fn max_maker_vol(mm: &MarketMakerIt, coin: &str) -> RpcResponse { let rc = mm .rpc(&json!({ @@ -2893,6 +2965,10 @@ pub async fn enable_tendermint( tx_history: bool, ) -> Json { let ibc_requests: Vec<_> = ibc_assets.iter().map(|ticker| json!({ "ticker": ticker })).collect(); + let nodes: Vec = rpc_urls + .iter() + .map(|u| json!({"url": u, "komodo_proxy": false })) + .collect(); let request = json!({ "userpass": mm.userpass, @@ -2901,7 +2977,7 @@ pub async fn enable_tendermint( "params": { "ticker": coin, "tokens_params": ibc_requests, - "rpc_urls": rpc_urls, + "nodes": nodes, "tx_history": tx_history } }); @@ -2929,6 +3005,10 @@ pub async fn enable_tendermint_without_balance( tx_history: bool, ) -> Json { let ibc_requests: Vec<_> = ibc_assets.iter().map(|ticker| json!({ "ticker": ticker })).collect(); + let nodes: Vec = rpc_urls + .iter() + .map(|u| json!({"url": u, "komodo_proxy": false })) + .collect(); let request = json!({ "userpass": mm.userpass, @@ -2937,7 +3017,7 @@ pub async fn enable_tendermint_without_balance( "params": { "ticker": coin, "tokens_params": ibc_requests, - "rpc_urls": rpc_urls, + "nodes": nodes, "tx_history": tx_history, "get_balances": false } @@ -3185,6 +3265,103 @@ pub async fn enable_eth_with_tokens_v2( } } +async fn init_erc20_token( + mm: &MarketMakerIt, + ticker: &str, + protocol: Option, + path_to_address: Option, +) -> Result<(StatusCode, Json), Json> { + let (status, response, _) = mm.rpc(&json!({ + "userpass": mm.userpass, + "method": "task::enable_erc20::init", + "mmrpc": "2.0", + "params": { + "ticker": ticker, + "protocol": protocol, + "activation_params": { + "path_to_address": path_to_address.unwrap_or_default(), + } + } + })) + .await + .unwrap(); + + if status.is_success() { + Ok((status, json::from_str(&response).unwrap())) + } else { + Err(json::from_str(&response).unwrap()) + } +} + +async fn init_erc20_token_status(mm: &MarketMakerIt, task_id: u64) -> Json { + let request = mm + .rpc(&json!({ + "userpass": mm.userpass, + "method": "task::enable_erc20::status", + "mmrpc": "2.0", + "params": { + "task_id": task_id, + } + })) + .await + .unwrap(); + assert_eq!( + request.0, + StatusCode::OK, + "'task::enable_erc20::status' failed: {}", + request.1 + ); + json::from_str(&request.1).unwrap() +} + +pub async fn enable_erc20_token_v2( + mm: &MarketMakerIt, + ticker: &str, + protocol: Option, + timeout: u64, + path_to_address: Option, +) -> Result { + let init = init_erc20_token(mm, ticker, protocol, path_to_address).await?.1; + let init: RpcV2Response = json::from_value(init).unwrap(); + let timeout = wait_until_ms(timeout * 1000); + + loop { + if now_ms() > timeout { + panic!("{} initialization timed out", ticker); + } + + let status = init_erc20_token_status(mm, init.result.task_id).await; + let status: RpcV2Response = json::from_value(status).unwrap(); + match status.result { + InitErc20TokenStatus::Ok(result) => break Ok(result), + InitErc20TokenStatus::Error(e) => break Err(e), + _ => Timer::sleep(1.).await, + } + } +} + +pub async fn get_token_info(mm: &MarketMakerIt, protocol: Json) -> TokenInfoResponse { + let response = mm + .rpc(&json!({ + "userpass": mm.userpass, + "method": "get_token_info", + "mmrpc": "2.0", + "params": { + "protocol": protocol, + } + })) + .await + .unwrap(); + assert_eq!( + response.0, + StatusCode::OK, + "'get_token_info' failed: {}", + response.1 + ); + let response_json: Json = json::from_str(&response.1).unwrap(); + json::from_value(response_json["result"].clone()).unwrap() +} + /// Note that mm2 ignores `volume` if `max` is true. pub async fn set_price( mm: &MarketMakerIt, diff --git a/mm2src/mm2_test_helpers/src/structs.rs b/mm2src/mm2_test_helpers/src/structs.rs index a8742d4ea2..baba173461 100644 --- a/mm2src/mm2_test_helpers/src/structs.rs +++ b/mm2src/mm2_test_helpers/src/structs.rs @@ -463,6 +463,7 @@ pub enum TransactionType { msg_type: CustomTendermintMsgType, token_id: Option, }, + TendermintIBCTransfer, } #[derive(Debug, Deserialize, PartialEq, Serialize)] @@ -620,7 +621,7 @@ pub struct ZCoinActivationResult { #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] pub struct GetNewAddressResponse { - pub new_address: HDAddressBalance, + pub new_address: HDAddressBalanceMap, } #[derive(Debug, Deserialize)] @@ -628,8 +629,8 @@ pub struct GetNewAddressResponse { pub struct HDAccountBalanceResponse { pub account_index: u32, pub derivation_path: String, - pub addresses: Vec, - pub page_balance: CoinBalance, + pub addresses: Vec, + pub page_balance: HashMap, pub limit: usize, pub skipped: u32, pub total: u32, @@ -650,7 +651,7 @@ pub struct CoinActivationResult { pub struct UtxoStandardActivationResult { pub ticker: String, pub current_block: u64, - pub wallet_balance: EnableCoinBalance, + pub wallet_balance: EnableCoinBalanceMap, } #[derive(Debug, Deserialize)] @@ -713,6 +714,15 @@ pub enum InitEthWithTokensStatus { UserActionRequired(Json), } +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields, tag = "status", content = "details")] +pub enum InitErc20TokenStatus { + Ok(InitTokenActivationResult), + Error(Json), + InProgress(Json), + UserActionRequired(Json), +} + #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields, tag = "status", content = "details")] pub enum InitLightningStatus { @@ -725,7 +735,7 @@ pub enum InitLightningStatus { #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields, tag = "status", content = "details")] pub enum CreateNewAccountStatus { - Ok(HDAccountBalance), + Ok(HDAccountBalanceMap), Error(Json), InProgress(Json), UserActionRequired(Json), @@ -826,6 +836,13 @@ pub struct GetSharedDbIdResult { pub shared_db_id: String, } +#[derive(Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct GetWalletNamesResult { + pub wallet_names: Vec, + pub activated_wallet: Option, +} + #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] pub struct RpcV2Response { @@ -905,18 +922,21 @@ pub enum EthWithTokensActivationResult { #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] -pub struct EnableBchWithTokensResponse { +pub struct InitTokenActivationResult { + pub ticker: String, + pub platform_coin: String, + pub token_contract_address: String, pub current_block: u64, - pub bch_addresses_infos: HashMap>, - pub slp_addresses_infos: HashMap>, + pub required_confirmations: u64, + pub wallet_balance: EnableCoinBalanceMap, } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] -pub struct EnableSolanaWithTokensResponse { +pub struct EnableBchWithTokensResponse { pub current_block: u64, - pub solana_addresses_infos: HashMap>, - pub spl_addresses_infos: HashMap>, + pub bch_addresses_infos: HashMap>, + pub slp_addresses_infos: HashMap>, } #[derive(Debug, Deserialize)] @@ -1186,3 +1206,25 @@ pub struct ActiveSwapsResponse { pub uuids: Vec, pub statuses: Option>, } + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Erc20TokenInfo { + pub symbol: String, + pub decimals: u8, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(tag = "type", content = "info")] +pub enum TokenInfo { + ERC20(Erc20TokenInfo), +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct TokenInfoResponse { + pub config_ticker: Option, + #[serde(flatten)] + pub info: TokenInfo, +} diff --git a/mm2src/proxy_signature/Cargo.toml b/mm2src/proxy_signature/Cargo.toml new file mode 100644 index 0000000000..5392b9862a --- /dev/null +++ b/mm2src/proxy_signature/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "proxy_signature" +version = "0.1.0" +edition = "2018" + +[dependencies] +chrono = "0.4" +http = "0.2" +libp2p = { git = "https://github.com/KomodoPlatform/rust-libp2p.git", tag = "k-0.52.4", default-features = false, features = ["identify"] } +serde = "1" +serde_json = { version = "1", features = ["preserve_order", "raw_value"] } + +[dev-dependencies] +rand = { version = "0.7", features = ["std", "small_rng"] } diff --git a/mm2src/proxy_signature/src/lib.rs b/mm2src/proxy_signature/src/lib.rs new file mode 100644 index 0000000000..15cf596c89 --- /dev/null +++ b/mm2src/proxy_signature/src/lib.rs @@ -0,0 +1,165 @@ +use chrono::Utc; +use http::Uri; +use libp2p::identity::{Keypair, PublicKey, SigningError}; +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; + +/// Represents a message and its corresponding signature. +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct ProxySign { + /// Signature of the raw message. + pub signature_bytes: Vec, + /// Unique address of the sign's owner. + pub address: String, + /// The raw message that has been signed. + pub raw_message: RawMessage, +} + +/// Essential type that contains information required for generating signed messages (see `ProxySign`). +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct RawMessage { + /// This field is used to verify the proxy sign on the Komodo DeFi proxy side. + pub uri: String, + /// This field is used to check the payload size on the komodo-defi-proxy side. + /// Along with the `uri` field it helps confirm that the proxy sign matches the request. + pub body_size: usize, + pub public_key_encoded: Vec, + pub expires_at: i64, +} + +impl RawMessage { + fn new(uri: &Uri, body_size: usize, public_key_encoded: Vec, expires_in_seconds: i64) -> Self { + RawMessage { + uri: uri.to_string(), + body_size, + public_key_encoded, + expires_at: Utc::now().timestamp() + expires_in_seconds, + } + } + + /// Generates a byte vector representation of `self`. + fn encode(&self) -> Vec { + const PREFIX: &str = "Encoded Message for KDP\n"; + let mut bytes = PREFIX.as_bytes().to_owned(); + bytes.extend(self.public_key_encoded.clone()); + bytes.extend(self.uri.to_string().as_bytes().to_owned()); + bytes.extend(self.body_size.to_ne_bytes().to_owned()); + bytes.extend(self.expires_at.to_ne_bytes().to_owned()); + bytes + } + + /// Generates `ProxySign` using the provided keypair and coin ticker. + pub fn sign( + keypair: &Keypair, + uri: &Uri, + body_size: usize, + expires_in_seconds: i64, + ) -> Result { + let public_key_encoded = keypair.public().encode_protobuf(); + let address = keypair.public().to_peer_id().to_string(); + let raw_message = RawMessage::new(uri, body_size, public_key_encoded, expires_in_seconds); + let signature_bytes = keypair.sign(&raw_message.encode())?; + + Ok(ProxySign { + raw_message, + address, + signature_bytes, + }) + } +} + +impl ProxySign { + /// Validates if the message is still valid based on its expiration time and signature verification. + pub fn is_valid_message(&self, max_message_exp_secs: u64) -> bool { + let now = Utc::now().timestamp(); + let remaining_expiration_seconds = u64::try_from(self.raw_message.expires_at - now).unwrap_or(0); + + if remaining_expiration_seconds == 0 || remaining_expiration_seconds > max_message_exp_secs { + return false; + } + + let Ok(public_key) = PublicKey::try_decode_protobuf(&self.raw_message.public_key_encoded) else { + return false; + }; + + if self.address != public_key.to_peer_id().to_string() { + return false; + } + + public_key.verify(&self.raw_message.encode(), &self.signature_bytes) + } +} + +#[cfg(test)] +pub mod proxy_signature_tests { + use libp2p::identity; + use rand::RngCore; + + use super::*; + + fn generate_ed25519_keypair(mut p2p_key: [u8; 32]) -> identity::Keypair { + let secret = identity::ed25519::SecretKey::try_from_bytes(&mut p2p_key).expect("Secret length is 32 bytes"); + let keypair = identity::ed25519::Keypair::from(secret); + identity::Keypair::from(keypair) + } + + fn os_rng(dest: &mut [u8]) -> Result<(), rand::Error> { rand::rngs::OsRng.try_fill_bytes(dest) } + + fn random_keypair() -> Keypair { + let mut p2p_key = [0u8; 32]; + os_rng(&mut p2p_key).unwrap(); + generate_ed25519_keypair(p2p_key) + } + + #[test] + fn sign_and_verify() { + let keypair = random_keypair(); + let signed_proxy_message = RawMessage::sign(&keypair, &Uri::from_static("http://example.com"), 0, 5).unwrap(); + assert!(signed_proxy_message.is_valid_message(10)); + } + + #[test] + fn expired_signature() { + let keypair = random_keypair(); + let signed_proxy_message = RawMessage::sign(&keypair, &Uri::from_static("http://example.com"), 0, -1).unwrap(); + assert!(!signed_proxy_message.is_valid_message(10)); + } + + #[test] + fn dirty_raw_message() { + let keypair = random_keypair(); + let mut signed_proxy_message = + RawMessage::sign(&keypair, &Uri::from_static("http://example.com"), 0, 5).unwrap(); + signed_proxy_message.raw_message.uri = "http://demo.com".to_string(); + assert!(!signed_proxy_message.is_valid_message(10)); + + let mut signed_proxy_message = + RawMessage::sign(&keypair, &Uri::from_static("http://example.com"), 0, 5).unwrap(); + signed_proxy_message.raw_message.body_size += 1; + assert!(!signed_proxy_message.is_valid_message(10)); + + let mut signed_proxy_message = + RawMessage::sign(&keypair, &Uri::from_static("http://example.com"), 0, 5).unwrap(); + signed_proxy_message.raw_message.expires_at += 1; + assert!(!signed_proxy_message.is_valid_message(10)); + } + + #[test] + fn message_lifetime_overflow() { + let keypair = random_keypair(); + let signed_proxy_message = RawMessage::sign(&keypair, &Uri::from_static("http://example.com"), 0, 5).unwrap(); + assert!(!signed_proxy_message.is_valid_message(4)); + } + + #[test] + fn verify_peer_id() { + let expected_address = "12D3KooWJPtxrHVDPoETNfPJY4WWVzX7Ti4WPemtXDgb5qmFrDiv"; + + let p2p_key = [123u8; 32]; + let keypair = generate_ed25519_keypair(p2p_key); + assert_eq!(keypair.public().to_peer_id().to_string(), expected_address); + + let signed_proxy_message = RawMessage::sign(&keypair, &Uri::from_static("http://example.com"), 0, 5).unwrap(); + assert_eq!(signed_proxy_message.address, expected_address); + } +} diff --git a/mm2src/trading_api/Cargo.toml b/mm2src/trading_api/Cargo.toml new file mode 100644 index 0000000000..4fd9514fb9 --- /dev/null +++ b/mm2src/trading_api/Cargo.toml @@ -0,0 +1,28 @@ +[package] +# integration with external trading api +name = "trading_api" +version = "0.1.0" +edition = "2018" + +[dependencies] +common = { path = "../common" } +enum_derives = { path = "../derives/enum_derives" } +mm2_core = { path = "../mm2_core" } +mm2_err_handle = { path = "../mm2_err_handle" } +mm2_net = { path = "../mm2_net" } +mm2_number = { path = "../mm2_number" } +mocktopus = { version = "0.8.0", optional = true } + +derive_more = "0.99" +ethereum-types = { version = "0.13", default-features = false, features = ["std", "serialize"] } +lazy_static = "1.4" +serde = "1.0" +serde_derive = "1.0" +serde_json = { version = "1", features = ["preserve_order", "raw_value"] } +url = { version = "2.2.2", features = ["serde"] } + +[features] +test-ext-api = [] # use test config to connect to an external api + +[dev-dependencies] +mocktopus = { version = "0.8.0" } \ No newline at end of file diff --git a/mm2src/trading_api/src/lib.rs b/mm2src/trading_api/src/lib.rs new file mode 100644 index 0000000000..183e6d9bcd --- /dev/null +++ b/mm2src/trading_api/src/lib.rs @@ -0,0 +1,3 @@ +//! This module is for indirect connection to third-party trading APIs, processing their results and errors + +pub mod one_inch_api; diff --git a/mm2src/trading_api/src/one_inch_api.rs b/mm2src/trading_api/src/one_inch_api.rs new file mode 100644 index 0000000000..9b0af1625e --- /dev/null +++ b/mm2src/trading_api/src/one_inch_api.rs @@ -0,0 +1,5 @@ +//! Wrapper for 1inch API. + +pub mod client; +pub mod errors; +pub mod types; diff --git a/mm2src/trading_api/src/one_inch_api/client.rs b/mm2src/trading_api/src/one_inch_api/client.rs new file mode 100644 index 0000000000..9c7136148a --- /dev/null +++ b/mm2src/trading_api/src/one_inch_api/client.rs @@ -0,0 +1,176 @@ +use super::errors::ApiClientError; +use crate::one_inch_api::errors::NativeError; +use common::StatusCode; +#[cfg(feature = "test-ext-api")] use lazy_static::lazy_static; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::{map_mm_error::MapMmError, + map_to_mm::MapToMmResult, + mm_error::{MmError, MmResult}}; +use mm2_net::transport::slurp_url_with_headers; +use serde::de::DeserializeOwned; +use url::Url; + +#[cfg(any(test, feature = "mocktopus"))] +use mocktopus::macros::*; + +const ONE_INCH_API_ENDPOINT_V6_0: &str = "swap/v6.0/"; +const SWAP_METHOD: &str = "swap"; +const QUOTE_METHOD: &str = "quote"; +const LIQUIDITY_SOURCES_METHOD: &str = "liquidity-sources"; +const TOKENS_METHOD: &str = "tokens"; + +const ONE_INCH_AGGREGATION_ROUTER_CONTRACT_V6_0: &str = "0x111111125421ca6dc452d289314280a0f8842a65"; +const ONE_INCH_ETH_SPECIAL_CONTRACT: &str = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + +#[cfg(test)] +const ONE_INCH_API_TEST_URL: &str = "https://api.1inch.dev"; + +#[cfg(feature = "test-ext-api")] +lazy_static! { + /// API key for testing + static ref ONE_INCH_API_TEST_AUTH: String = std::env::var("ONE_INCH_API_TEST_AUTH").unwrap_or_default(); +} + +pub(crate) type QueryParams<'life> = Vec<(&'life str, String)>; + +/// 1inch v6.0 supported eth-based chains +const ONE_INCH_V6_0_SUPPORTED_CHAINS: &[(&str, u64)] = &[ + ("Ethereum", 1), + ("Optimism", 10), + ("BSC", 56), + ("Gnosis", 100), + ("Polygon", 137), + ("Fantom", 250), + ("ZkSync", 324), + ("Klaytn", 8217), + ("Base", 8453), + ("Arbitrum", 42161), + ("Avalanche", 43114), + ("Aurora", 1313161554), +]; + +pub(crate) struct UrlBuilder<'a> { + base_url: Url, + endpoint: &'a str, + chain_id: u64, + method_name: String, + query_params: QueryParams<'a>, +} + +impl<'a> UrlBuilder<'a> { + pub(crate) fn new(api_client: &ApiClient, chain_id: u64, method_name: String) -> Self { + Self { + base_url: api_client.base_url.clone(), + endpoint: ApiClient::get_swap_endpoint(), + chain_id, + method_name, + query_params: vec![], + } + } + + pub(crate) fn with_query_params(&mut self, mut more_params: QueryParams<'a>) -> &mut Self { + self.query_params.append(&mut more_params); + self + } + + #[allow(clippy::result_large_err)] + pub(crate) fn build(&self) -> MmResult { + let url = self + .base_url + .join(self.endpoint)? + .join(&format!("{}/", self.chain_id.to_string()))? + .join(self.method_name.as_str())?; + Ok(Url::parse_with_params( + url.as_str(), + self.query_params + .iter() + .map(|v| (v.0, v.1.as_str())) + .collect::>(), + )?) + } +} + +/// 1-inch API caller +pub struct ApiClient { + base_url: Url, +} + +#[allow(clippy::swap_ptr_to_ref)] // need for moctopus +#[cfg_attr(any(test, feature = "mocktopus"), mockable)] +impl ApiClient { + #[allow(unused_variables)] + #[allow(clippy::result_large_err)] + pub fn new(ctx: MmArc) -> MmResult { + #[cfg(not(test))] + let url_cfg = ctx.conf["1inch_api"] + .as_str() + .ok_or(ApiClientError::InvalidParam("No API config param".to_owned()))?; + + #[cfg(test)] + let url_cfg = ONE_INCH_API_TEST_URL; + + Ok(Self { + base_url: Url::parse(url_cfg)?, + }) + } + + pub const fn eth_special_contract() -> &'static str { ONE_INCH_ETH_SPECIAL_CONTRACT } + + pub const fn classic_swap_contract() -> &'static str { ONE_INCH_AGGREGATION_ROUTER_CONTRACT_V6_0 } + + pub fn is_chain_supported(chain_id: u64) -> bool { + ONE_INCH_V6_0_SUPPORTED_CHAINS.iter().any(|(_name, id)| *id == chain_id) + } + + fn get_headers() -> Vec<(&'static str, &'static str)> { + vec![ + #[cfg(feature = "test-ext-api")] + ("Authorization", ONE_INCH_API_TEST_AUTH.as_str()), + ("accept", "application/json"), + ] + } + + fn get_swap_endpoint() -> &'static str { ONE_INCH_API_ENDPOINT_V6_0 } + + pub const fn get_swap_method() -> &'static str { SWAP_METHOD } + + pub const fn get_quote_method() -> &'static str { QUOTE_METHOD } + + pub const fn get_liquidity_sources_method() -> &'static str { LIQUIDITY_SOURCES_METHOD } + + pub const fn get_tokens_method() -> &'static str { TOKENS_METHOD } + + pub(crate) async fn call_api(api_url: &Url) -> MmResult { + let (status_code, _, body) = slurp_url_with_headers(api_url.as_str(), ApiClient::get_headers()) + .await + .mm_err(ApiClientError::TransportError)?; + let body = serde_json::from_slice(&body).map_to_mm(|err| ApiClientError::ParseBodyError { + error_msg: err.to_string(), + })?; + if status_code != StatusCode::OK { + let error = NativeError::new(status_code, body); + return Err(MmError::new(ApiClientError::from_native_error(error))); + } + serde_json::from_value(body).map_err(|err| { + ApiClientError::ParseBodyError { + error_msg: err.to_string(), + } + .into() + }) + } + + pub async fn call_swap_api<'l, T: DeserializeOwned>( + &self, + chain_id: u64, + method: String, + params: Option>, + ) -> MmResult { + let mut builder = UrlBuilder::new(self, chain_id, method); + if let Some(params) = params { + builder.with_query_params(params); + } + let api_url = builder.build()?; + + ApiClient::call_api(&api_url).await + } +} diff --git a/mm2src/trading_api/src/one_inch_api/errors.rs b/mm2src/trading_api/src/one_inch_api/errors.rs new file mode 100644 index 0000000000..d92f8e144b --- /dev/null +++ b/mm2src/trading_api/src/one_inch_api/errors.rs @@ -0,0 +1,130 @@ +use common::StatusCode; +use derive_more::Display; +use enum_derives::EnumFromStringify; +use ethereum_types::U256; +use mm2_net::transport::SlurpError; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Debug, Display, Serialize, EnumFromStringify)] +pub enum ApiClientError { + #[from_stringify("url::ParseError")] + InvalidParam(String), + #[display(fmt = "Parameter {param} out of bounds, value: {value}, min: {min} max: {max}")] + OutOfBounds { + param: String, + value: String, + min: String, + max: String, + }, + TransportError(SlurpError), + ParseBodyError { + error_msg: String, + }, + #[display(fmt = "General API error: {error_msg} description: {description}")] + GeneralApiError { + error_msg: String, + description: String, + status_code: u16, + }, + #[display(fmt = "Allowance not enough, needed: {amount} allowance: {allowance}")] + AllowanceNotEnough { + error_msg: String, + description: String, + status_code: u16, + /// Amount to approve for the API contract + amount: U256, + /// Existing allowance for the API contract + allowance: U256, + }, +} + +// API error meta 'type' field known values +const META_TYPE_ALLOWANCE: &str = "allowance"; +const META_TYPE_AMOUNT: &str = "amount"; + +#[derive(Debug, Deserialize)] +pub(crate) struct Error400 { + pub error: String, + pub description: Option, + #[serde(rename = "statusCode")] + pub status_code: u16, + pub meta: Option>, + #[allow(dead_code)] + #[serde(rename = "requestId")] + pub request_id: Option, +} + +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct Meta { + #[serde(rename = "type")] + pub meta_type: String, + #[serde(rename = "value")] + pub meta_value: String, +} + +#[derive(Debug)] +pub(crate) enum NativeError { + HttpError { error_msg: String, status_code: u16 }, + HttpError400(Error400), + ParseError { error_msg: String }, +} + +impl NativeError { + pub(crate) fn new(status_code: StatusCode, body: Value) -> Self { + if status_code == StatusCode::BAD_REQUEST { + match serde_json::from_value(body) { + Ok(err) => Self::HttpError400(err), + Err(err) => Self::ParseError { + error_msg: format!("could not parse error response: {}", err.to_string()), + }, + } + } else { + Self::HttpError { + error_msg: body["error"].as_str().unwrap_or_default().to_owned(), + status_code: status_code.into(), + } + } + } +} + +impl ApiClientError { + /// Convert from native API errors to lib errors + /// Look for known API errors. If none found return as general API error + pub(crate) fn from_native_error(api_error: NativeError) -> ApiClientError { + match api_error { + NativeError::HttpError400(error_400) => { + if let Some(meta) = error_400.meta { + // Try if it's "Not enough allowance" error 'meta' data: + if let Some(meta_allowance) = meta.iter().find(|m| m.meta_type == META_TYPE_ALLOWANCE) { + // try find 'amount' value + let amount = if let Some(meta_amount) = meta.iter().find(|m| m.meta_type == META_TYPE_AMOUNT) { + U256::from_dec_str(&meta_amount.meta_value).unwrap_or_default() + } else { + Default::default() + }; + let allowance = U256::from_dec_str(&meta_allowance.meta_value).unwrap_or_default(); + return ApiClientError::AllowanceNotEnough { + error_msg: error_400.error, + status_code: error_400.status_code, + description: error_400.description.unwrap_or_default(), + amount, + allowance, + }; + } + } + ApiClientError::GeneralApiError { + error_msg: error_400.error, + status_code: error_400.status_code, + description: error_400.description.unwrap_or_default(), + } + }, + NativeError::HttpError { error_msg, status_code } => ApiClientError::GeneralApiError { + error_msg, + status_code, + description: Default::default(), + }, + NativeError::ParseError { error_msg } => ApiClientError::ParseBodyError { error_msg }, + } + } +} diff --git a/mm2src/trading_api/src/one_inch_api/types.rs b/mm2src/trading_api/src/one_inch_api/types.rs new file mode 100644 index 0000000000..f13e943768 --- /dev/null +++ b/mm2src/trading_api/src/one_inch_api/types.rs @@ -0,0 +1,411 @@ +#![allow(clippy::result_large_err)] + +use super::client::QueryParams; +use super::errors::ApiClientError; +use common::{def_with_opt_param, push_if_some}; +use ethereum_types::Address; +use mm2_err_handle::mm_error::{MmError, MmResult}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use url::Url; + +const ONE_INCH_MAX_SLIPPAGE: f32 = 50.0; +const ONE_INCH_MAX_FEE_SHARE: f32 = 3.0; +const ONE_INCH_MAX_GAS: u128 = 11500000; +const ONE_INCH_MAX_PARTS: u32 = 100; +const ONE_INCH_MAX_MAIN_ROUTE_PARTS: u32 = 50; +const ONE_INCH_MAX_COMPLEXITY_LEVEL: u32 = 3; + +const BAD_URL_IN_RESPONSE_ERROR: &str = "unsupported url in response"; +const ONE_INCH_DOMAIN: &str = "1inch.io"; + +/// API params builder for swap quote +#[derive(Default)] +pub struct ClassicSwapQuoteParams { + /// Source token address + src: String, + /// Destination token address + dst: String, + amount: String, + // Optional fields + fee: Option, + protocols: Option, + gas_price: Option, + complexity_level: Option, + parts: Option, + main_route_parts: Option, + gas_limit: Option, + include_tokens_info: Option, + include_protocols: Option, + include_gas: Option, + connector_tokens: Option, +} + +impl ClassicSwapQuoteParams { + pub fn new(src: String, dst: String, amount: String) -> Self { + Self { + src, + dst, + amount, + ..Default::default() + } + } + + def_with_opt_param!(fee, f32); + def_with_opt_param!(protocols, String); + def_with_opt_param!(gas_price, String); + def_with_opt_param!(complexity_level, u32); + def_with_opt_param!(parts, u32); + def_with_opt_param!(main_route_parts, u32); + def_with_opt_param!(gas_limit, u128); + def_with_opt_param!(include_tokens_info, bool); + def_with_opt_param!(include_protocols, bool); + def_with_opt_param!(include_gas, bool); + def_with_opt_param!(connector_tokens, String); + + pub fn build_query_params(&self) -> MmResult, ApiClientError> { + self.validate_params()?; + + let mut params = vec![ + ("src", self.src.clone()), + ("dst", self.dst.clone()), + ("amount", self.amount.clone()), + ]; + + push_if_some!(params, "fee", self.fee); + push_if_some!(params, "protocols", &self.protocols); + push_if_some!(params, "gasPrice", &self.gas_price); + push_if_some!(params, "complexityLevel", self.complexity_level); + push_if_some!(params, "parts", self.parts); + push_if_some!(params, "mainRouteParts", self.main_route_parts); + push_if_some!(params, "gasLimit", self.gas_limit); + push_if_some!(params, "includeTokensInfo", self.include_tokens_info); + push_if_some!(params, "includeProtocols", self.include_protocols); + push_if_some!(params, "includeGas", self.include_gas); + push_if_some!(params, "connectorTokens", &self.connector_tokens); + Ok(params) + } + + /// Validate params by 1inch rules (to avoid extra requests) + fn validate_params(&self) -> MmResult<(), ApiClientError> { + validate_fee(&self.fee)?; + validate_complexity_level(&self.complexity_level)?; + validate_gas_limit(&self.gas_limit)?; + validate_parts(&self.parts)?; + validate_main_route_parts(&self.main_route_parts)?; + Ok(()) + } +} + +/// API params builder to create a tx for swap +#[derive(Default)] +pub struct ClassicSwapCreateParams { + src: String, + dst: String, + amount: String, + from: String, + slippage: f32, + // Optional fields + fee: Option, + protocols: Option, + gas_price: Option, + complexity_level: Option, + parts: Option, + main_route_parts: Option, + gas_limit: Option, + include_tokens_info: Option, + include_protocols: Option, + include_gas: Option, + connector_tokens: Option, + excluded_protocols: Option, + permit: Option, + compatibility: Option, + receiver: Option, + referrer: Option, + disable_estimate: Option, + allow_partial_fill: Option, + use_permit2: Option, +} + +impl ClassicSwapCreateParams { + pub fn new(src: String, dst: String, amount: String, from: String, slippage: f32) -> Self { + Self { + src, + dst, + amount, + from, + slippage, + ..Default::default() + } + } + + def_with_opt_param!(fee, f32); + def_with_opt_param!(protocols, String); + def_with_opt_param!(gas_price, String); + def_with_opt_param!(complexity_level, u32); + def_with_opt_param!(parts, u32); + def_with_opt_param!(main_route_parts, u32); + def_with_opt_param!(gas_limit, u128); + def_with_opt_param!(include_tokens_info, bool); + def_with_opt_param!(include_protocols, bool); + def_with_opt_param!(include_gas, bool); + def_with_opt_param!(connector_tokens, String); + def_with_opt_param!(excluded_protocols, String); + def_with_opt_param!(permit, String); + def_with_opt_param!(compatibility, bool); + def_with_opt_param!(receiver, String); + def_with_opt_param!(referrer, String); + def_with_opt_param!(disable_estimate, bool); + def_with_opt_param!(allow_partial_fill, bool); + def_with_opt_param!(use_permit2, bool); + + pub fn build_query_params(&self) -> MmResult, ApiClientError> { + self.validate_params()?; + + let mut params = vec![ + ("src", self.src.clone()), + ("dst", self.dst.clone()), + ("amount", self.amount.clone()), + ("from", self.from.clone()), + ("slippage", self.slippage.to_string()), + ]; + + push_if_some!(params, "fee", self.fee); + push_if_some!(params, "protocols", &self.protocols); + push_if_some!(params, "gasPrice", &self.gas_price); + push_if_some!(params, "complexityLevel", self.complexity_level); + push_if_some!(params, "parts", self.parts); + push_if_some!(params, "mainRouteParts", self.main_route_parts); + push_if_some!(params, "gasLimit", self.gas_limit); + push_if_some!(params, "includeTokensInfo", self.include_tokens_info); + push_if_some!(params, "includeProtocols", self.include_protocols); + push_if_some!(params, "includeGas", self.include_gas); + push_if_some!(params, "connectorTokens", &self.connector_tokens); + push_if_some!(params, "excludedProtocols", &self.excluded_protocols); + push_if_some!(params, "permit", &self.permit); + push_if_some!(params, "compatibility", &self.compatibility); + push_if_some!(params, "receiver", &self.receiver); + push_if_some!(params, "referrer", &self.referrer); + push_if_some!(params, "disableEstimate", self.disable_estimate); + push_if_some!(params, "allowPartialFill", self.allow_partial_fill); + push_if_some!(params, "usePermit2", self.use_permit2); + + Ok(params) + } + + /// Validate params by 1inch rules (to avoid extra requests) + fn validate_params(&self) -> MmResult<(), ApiClientError> { + validate_slippage(self.slippage)?; + validate_fee(&self.fee)?; + validate_complexity_level(&self.complexity_level)?; + validate_gas_limit(&self.gas_limit)?; + validate_parts(&self.parts)?; + validate_main_route_parts(&self.main_route_parts)?; + Ok(()) + } +} + +#[derive(Deserialize, Debug, Serialize)] +pub struct TokenInfo { + pub address: Address, + pub symbol: String, + pub name: String, + pub decimals: u32, + pub eip2612: bool, + #[serde(rename = "isFoT", default)] + pub is_fot: bool, + #[serde(rename = "logoURI", with = "serde_one_inch_link")] + pub logo_uri: String, + pub tags: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ProtocolInfo { + pub name: String, + pub part: f64, + #[serde(rename = "fromTokenAddress")] + pub from_token_address: Address, + #[serde(rename = "toTokenAddress")] + pub to_token_address: Address, +} + +#[derive(Deserialize, Debug)] +pub struct ClassicSwapData { + /// dst token amount to receive, in api is a decimal number as string + #[serde(rename = "dstAmount")] + pub dst_amount: String, + #[serde(rename = "srcToken")] + pub src_token: Option, + #[serde(rename = "dstToken")] + pub dst_token: Option, + pub protocols: Option>>>, + pub tx: Option, + pub gas: Option, +} + +#[derive(Deserialize, Debug)] +pub struct TxFields { + pub from: Address, + pub to: Address, + pub data: String, + /// tx value, in api is a decimal number as string + pub value: String, + /// gas price, in api is a decimal number as string + #[serde(rename = "gasPrice")] + pub gas_price: String, + /// gas limit, in api is a decimal number + pub gas: u128, +} + +#[derive(Deserialize, Serialize)] +pub struct ProtocolImage { + pub id: String, + pub title: String, + #[serde(with = "serde_one_inch_link")] + pub img: String, + #[serde(with = "serde_one_inch_link")] + pub img_color: String, +} + +#[derive(Deserialize)] +pub struct ProtocolsResponse { + pub protocols: Vec, +} + +#[derive(Deserialize)] +pub struct TokensResponse { + pub tokens: HashMap, +} + +mod serde_one_inch_link { + use super::validate_one_inch_link; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + /// Just forward to the normal serializer + pub(super) fn serialize(s: &String, serializer: S) -> Result + where + S: Serializer, + { + s.serialize(serializer) + } + + /// Deserialise String with checking links + pub(super) fn deserialize<'a, D>(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + ::deserialize(deserializer) + .map(|value| validate_one_inch_link(&value).unwrap_or_default()) + } +} + +fn validate_slippage(slippage: f32) -> MmResult<(), ApiClientError> { + if !(0.0..=ONE_INCH_MAX_SLIPPAGE).contains(&slippage) { + return Err(ApiClientError::OutOfBounds { + param: "slippage".to_owned(), + value: slippage.to_string(), + min: 0.0.to_string(), + max: ONE_INCH_MAX_SLIPPAGE.to_string(), + } + .into()); + } + Ok(()) +} + +fn validate_fee(fee: &Option) -> MmResult<(), ApiClientError> { + if let Some(fee) = fee { + if !(0.0..=ONE_INCH_MAX_FEE_SHARE).contains(fee) { + return Err(ApiClientError::OutOfBounds { + param: "fee".to_owned(), + value: fee.to_string(), + min: 0.0.to_string(), + max: ONE_INCH_MAX_FEE_SHARE.to_string(), + } + .into()); + } + } + Ok(()) +} + +fn validate_gas_limit(gas_limit: &Option) -> MmResult<(), ApiClientError> { + if let Some(gas_limit) = gas_limit { + if gas_limit > &ONE_INCH_MAX_GAS { + return Err(ApiClientError::OutOfBounds { + param: "gas_limit".to_owned(), + value: gas_limit.to_string(), + min: 0.to_string(), + max: ONE_INCH_MAX_GAS.to_string(), + } + .into()); + } + } + Ok(()) +} + +fn validate_parts(parts: &Option) -> MmResult<(), ApiClientError> { + if let Some(parts) = parts { + if parts > &ONE_INCH_MAX_PARTS { + return Err(ApiClientError::OutOfBounds { + param: "parts".to_owned(), + value: parts.to_string(), + min: 0.to_string(), + max: ONE_INCH_MAX_PARTS.to_string(), + } + .into()); + } + } + Ok(()) +} + +fn validate_main_route_parts(main_route_parts: &Option) -> MmResult<(), ApiClientError> { + if let Some(main_route_parts) = main_route_parts { + if main_route_parts > &ONE_INCH_MAX_MAIN_ROUTE_PARTS { + return Err(ApiClientError::OutOfBounds { + param: "main route parts".to_owned(), + value: main_route_parts.to_string(), + min: 0.to_string(), + max: ONE_INCH_MAX_MAIN_ROUTE_PARTS.to_string(), + } + .into()); + } + } + Ok(()) +} + +fn validate_complexity_level(complexity_level: &Option) -> MmResult<(), ApiClientError> { + if let Some(complexity_level) = complexity_level { + if complexity_level > &ONE_INCH_MAX_COMPLEXITY_LEVEL { + return Err(ApiClientError::OutOfBounds { + param: "complexity level".to_owned(), + value: complexity_level.to_string(), + min: 0.to_string(), + max: ONE_INCH_MAX_COMPLEXITY_LEVEL.to_string(), + } + .into()); + } + } + Ok(()) +} + +/// Check if url is valid and is a subdomain of 1inch domain (simple anti-phishing check) +fn validate_one_inch_link(s: &str) -> MmResult { + let url = Url::parse(s).map_err(|_err| ApiClientError::ParseBodyError { + error_msg: BAD_URL_IN_RESPONSE_ERROR.to_owned(), + })?; + if let Some(host) = url.host() { + if host.to_string().ends_with(ONE_INCH_DOMAIN) { + return Ok(s.to_owned()); + } + } + MmError::err(ApiClientError::ParseBodyError { + error_msg: BAD_URL_IN_RESPONSE_ERROR.to_owned(), + }) +} + +#[test] +fn test_validate_one_inch_link() { + assert!(validate_one_inch_link("https://cdn.1inch.io/liquidity-sources-logo/wmatic_color.png").is_ok()); + assert!(validate_one_inch_link("https://example.org/somepath/somefile.png").is_err()); + assert!(validate_one_inch_link("https://inch.io/somepath/somefile.png").is_err()); + assert!(validate_one_inch_link("127.0.0.1").is_err()); +} diff --git a/mm2src/trezor/Cargo.toml b/mm2src/trezor/Cargo.toml index 5f0542d35a..36e5abb0ec 100644 --- a/mm2src/trezor/Cargo.toml +++ b/mm2src/trezor/Cargo.toml @@ -14,7 +14,7 @@ derive_more = "0.99" futures = { version = "0.3", package = "futures", features = ["compat", "async-await"] } hw_common = { path = "../hw_common" } mm2_err_handle = { path = "../mm2_err_handle" } -prost = "0.11" +prost = "0.12" rand = { version = "0.7", features = ["std", "wasm-bindgen"] } rpc_task = { path = "../rpc_task" } serde = "1.0" diff --git a/mm2src/trezor/src/error.rs b/mm2src/trezor/src/error.rs index 798185695c..6a67c592f2 100644 --- a/mm2src/trezor/src/error.rs +++ b/mm2src/trezor/src/error.rs @@ -1,3 +1,5 @@ +use std::convert::TryFrom; + use crate::proto::messages::MessageType; use crate::proto::messages_common::{failure::FailureType, Failure}; use crate::user_interaction::TrezorUserInteraction; @@ -57,7 +59,7 @@ pub enum OperationFailure { impl From for OperationFailure { fn from(failure: Failure) -> Self { - match failure.code.and_then(FailureType::from_i32) { + match failure.code.and_then(|t| FailureType::try_from(t).ok()) { Some(FailureType::FailurePinInvalid) | Some(FailureType::FailurePinMismatch) => { OperationFailure::InvalidPin }, diff --git a/mm2src/trezor/src/response.rs b/mm2src/trezor/src/response.rs index 869b910997..259db41840 100644 --- a/mm2src/trezor/src/response.rs +++ b/mm2src/trezor/src/response.rs @@ -6,6 +6,7 @@ use crate::{TrezorError, TrezorResult}; use async_trait::async_trait; use mm2_err_handle::prelude::*; use rpc_task::RpcTaskError; +use std::convert::TryFrom; use std::fmt; use std::sync::Arc; @@ -150,7 +151,10 @@ impl<'a, 'b, T> fmt::Debug for ButtonRequest<'a, 'b, T> { impl<'a, 'b, T: 'static> ButtonRequest<'a, 'b, T> { /// The type of button request. - pub fn request_type(&self) -> Option { self.message.code.and_then(ButtonRequestType::from_i32) } + #[inline(always)] + pub fn request_type(&self) -> Option { + self.message.code.and_then(|t| ButtonRequestType::try_from(t).ok()) + } /// Ack the request and get the next message from the device. pub async fn ack(self) -> TrezorResult> { @@ -174,8 +178,9 @@ impl<'a, 'b, T> fmt::Debug for PinMatrixRequest<'a, 'b, T> { impl<'a, 'b, T: 'static> PinMatrixRequest<'a, 'b, T> { /// The type of PIN matrix request. + #[inline(always)] pub fn request_type(&self) -> Option { - self.message.r#type.and_then(PinMatrixRequestType::from_i32) + self.message.r#type.and_then(|t| PinMatrixRequestType::try_from(t).ok()) } /// Ack the request with a PIN and get the next message from the device. diff --git a/mm2src/trezor/src/transport/protocol.rs b/mm2src/trezor/src/transport/protocol.rs index 6c553e5d76..e6c822eb3c 100644 --- a/mm2src/trezor/src/transport/protocol.rs +++ b/mm2src/trezor/src/transport/protocol.rs @@ -1,5 +1,7 @@ //! This file is inspired by https://github.com/tezedge/tezedge-client/blob/master/trezor_api/src/transport/protocol.rs +use std::convert::TryFrom; + use crate::proto::messages::MessageType; use crate::proto::ProtoMessage; use crate::{TrezorError, TrezorResult}; @@ -85,9 +87,10 @@ impl Protocol for ProtocolV1 { ); return MmError::err(TrezorError::ProtocolError(error)); } - let message_type_id = BigEndian::read_u16(&chunk[3..5]) as u32; - let message_type = MessageType::from_i32(message_type_id as i32) - .or_mm_err(|| TrezorError::ProtocolError(format!("Invalid message type: {}", message_type_id)))?; + let message_type_id = BigEndian::read_u16(&chunk[3..5]) as i32; + let message_type = MessageType::try_from(message_type_id).map_err(|e| { + TrezorError::ProtocolError(format!("Invalid message type: {}, Error: {}", message_type_id, e)) + })?; let data_length = BigEndian::read_u32(&chunk[5..9]) as usize; let mut data: Vec = chunk[9..].into(); diff --git a/mm2src/trezor/src/utxo/sign_utxo.rs b/mm2src/trezor/src/utxo/sign_utxo.rs index 8b56f4fbd4..7f53182639 100644 --- a/mm2src/trezor/src/utxo/sign_utxo.rs +++ b/mm2src/trezor/src/utxo/sign_utxo.rs @@ -1,3 +1,5 @@ +use std::convert::TryFrom; + use crate::proto::messages_bitcoin as proto_bitcoin; use crate::result_handler::ResultHandler; use crate::utxo::unsigned_tx::UnsignedUtxoTx; @@ -61,7 +63,9 @@ impl<'a> TrezorSession<'a> { loop { extract_serialized_data(&tx_request, &mut result)?; - let request_type = tx_request.request_type.and_then(ProtoTxRequestType::from_i32); + let request_type = tx_request + .request_type + .and_then(|t| ProtoTxRequestType::try_from(t).ok()); let request_type = match request_type { Some(ProtoTxRequestType::Txfinished) => return Ok(result), Some(req_type) => req_type, diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 0f36ce4d60..f1739addd6 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-10-29" -components = ["rustfmt", "clippy"] +channel = "nightly-2023-06-01" +components = ["rustfmt", "clippy", "rust-analyzer"] diff --git a/scripts/ci/upload_artifact.py b/scripts/ci/upload_artifact.py new file mode 100755 index 0000000000..7ab63fe449 --- /dev/null +++ b/scripts/ci/upload_artifact.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +import os +import paramiko +import sys +from io import StringIO + +def upload_files(remote_host, remote_user, remote_port, ssh_private_key, source, target): + privkey = StringIO(ssh_private_key) + ssh_key = paramiko.Ed25519Key.from_private_key(privkey) + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + try: + ssh.connect(remote_host, port=remote_port, username=remote_user, pkey=ssh_key) + + sftp = ssh.open_sftp() + for root, dirs, files in os.walk(source): + for filename in files: + local_path = os.path.join(root, filename) + relative_path = os.path.relpath(local_path, source) + remote_path = os.path.join(target, relative_path).replace("\\", "/") + + remote_dir = os.path.dirname(remote_path) + try: + sftp.mkdir(remote_dir) + except OSError: + pass # Directory probably already exists + + sftp.put(local_path, remote_path) + + print("Upload finished successfully") + ssh.close() + + except Exception as e: + print("Upload failed: " + str(e)) + sys.exit(1) + +def main(): + if len(sys.argv) != 3: + print("Usage: `python upload_artifact.py `") + sys.exit(1) + + source = sys.argv[1] + target = sys.argv[2] + + remote_host = os.getenv('FILE_SERVER_HOST') + remote_user = os.getenv('FILE_SERVER_USERNAME') + remote_port = int(os.getenv('FILE_SERVER_PORT', 22)) + ssh_private_key = os.getenv('FILE_SERVER_KEY') + + if not (remote_host or remote_user or remote_port or ssh_private_key): + print("Missing environment variables for SSH connection") + sys.exit(1) + + upload_files(remote_host, remote_user, remote_port, ssh_private_key, source, target) + +if __name__ == "__main__": + main() diff --git a/scripts/mm2/client/client b/scripts/mm2/client/client index 42198d3b2c..046dfbecf1 100755 --- a/scripts/mm2/client/client +++ b/scripts/mm2/client/client @@ -12,4 +12,4 @@ echo "0.0.0.0 8923 $(ip route|awk '/default/ { print $3 }') 8923" >> /tmp/rinetd echo "0.0.0.0 11608 $(ip route|awk '/default/ { print $3 }') 11608" >> /tmp/rinetd.conf /usr/sbin/rinetd -c /tmp/rinetd.conf -../../target/debug/mm2 "{\"netid\":9000,\"seednode\":\"$SEEDNODE\",\"gui\":\"nogui\",\"client\":1, \"userhome\":\"/${HOME#"/"}\", \"passphrase\":\"$passphrase\"}" +../../target/debug/kdf "{\"netid\":9000,\"seednode\":\"$SEEDNODE\",\"gui\":\"nogui\",\"client\":1, \"userhome\":\"/${HOME#"/"}\", \"passphrase\":\"$passphrase\"}" diff --git a/scripts/mm2/client/client_debug b/scripts/mm2/client/client_debug index 1473588f41..ad7a686015 100755 --- a/scripts/mm2/client/client_debug +++ b/scripts/mm2/client/client_debug @@ -15,4 +15,4 @@ echo "0.0.0.0 8923 $(ip route|awk '/default/ { print $3 }') 8923" >> /tmp/rinetd echo "0.0.0.0 11608 $(ip route|awk '/default/ { print $3 }') 11608" >> /tmp/rinetd.conf /usr/sbin/rinetd -c /tmp/rinetd.conf -gdbserver :4444 ../../target/debug/mm2 "{\"alice_contract\":\"0xe1d4236c5774d35dc47dcc2e5e0ccfc463a3289c\",\"bob_contract\":\"0x105aFE60fDC8B5c021092b09E8a042135A4A976E\",\"ethnode\":\"http://195.201.0.6:8545\",\"netid\":9999,\"seednode\":\"10.100.0.2\",\"gui\":\"nogui\",\"client\":1, \"userhome\":\"/${HOME#"/"}\", \"passphrase\":\"$passphrase\", \"coins\":$coins}" +gdbserver :4444 ../../target/debug/kdf "{\"alice_contract\":\"0xe1d4236c5774d35dc47dcc2e5e0ccfc463a3289c\",\"bob_contract\":\"0x105aFE60fDC8B5c021092b09E8a042135A4A976E\",\"ethnode\":\"http://195.201.0.6:8545\",\"netid\":9999,\"seednode\":\"10.100.0.2\",\"gui\":\"nogui\",\"client\":1, \"userhome\":\"/${HOME#"/"}\", \"passphrase\":\"$passphrase\", \"coins\":$coins}" diff --git a/scripts/mm2/seed/run b/scripts/mm2/seed/run index ed5a091f9a..36ee73155c 100755 --- a/scripts/mm2/seed/run +++ b/scripts/mm2/seed/run @@ -12,4 +12,4 @@ echo "0.0.0.0 8923 $(ip route|awk '/default/ { print $3 }') 8923" >> /tmp/rinetd echo "0.0.0.0 11608 $(ip route|awk '/default/ { print $3 }') 11608" >> /tmp/rinetd.conf /usr/sbin/rinetd -c /tmp/rinetd.conf -../../target/debug/mm2 "{\"netid\":9000,\"gui\":\"nogui\", \"profitmargin\":0.01, \"userhome\":\"/${HOME#"/"}\", \"passphrase\":\"$passphrase\", \"coins\":$coins}" +../../target/debug/kdf "{\"netid\":9000,\"gui\":\"nogui\", \"profitmargin\":0.01, \"userhome\":\"/${HOME#"/"}\", \"passphrase\":\"$passphrase\", \"coins\":$coins}" diff --git a/scripts/mm2/seed/run_debug b/scripts/mm2/seed/run_debug index 03b269c1a1..23174d9048 100755 --- a/scripts/mm2/seed/run_debug +++ b/scripts/mm2/seed/run_debug @@ -15,4 +15,4 @@ echo "0.0.0.0 8923 $(ip route|awk '/default/ { print $3 }') 8923" >> /tmp/rinetd echo "0.0.0.0 11608 $(ip route|awk '/default/ { print $3 }') 11608" >> /tmp/rinetd.conf /usr/sbin/rinetd -c /tmp/rinetd.conf -gdbserver :4444 ../../target/debug/mm2 "{\"alice_contract\":\"0xe1d4236c5774d35dc47dcc2e5e0ccfc463a3289c\",\"bob_contract\":\"0x105aFE60fDC8B5c021092b09E8a042135A4A976E\",\"ethnode\":\"http://195.201.0.6:8545\",\"netid\":9999,\"gui\":\"nogui\", \"profitmargin\":0.01, \"userhome\":\"/${HOME#"/"}\", \"passphrase\":\"$passphrase\", \"coins\":$coins}" +gdbserver :4444 ../../target/debug/kdf "{\"alice_contract\":\"0xe1d4236c5774d35dc47dcc2e5e0ccfc463a3289c\",\"bob_contract\":\"0x105aFE60fDC8B5c021092b09E8a042135A4A976E\",\"ethnode\":\"http://195.201.0.6:8545\",\"netid\":9999,\"gui\":\"nogui\", \"profitmargin\":0.01, \"userhome\":\"/${HOME#"/"}\", \"passphrase\":\"$passphrase\", \"coins\":$coins}"