kopia lustrzana https://github.com/bugout-dev/moonstream
Merge branch 'main' into status-page
commit
2d188d0f5d
|
@ -0,0 +1,45 @@
|
|||
name: Publish Moonstream Python client library
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "clients/python/v*"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: clients/python
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e .[distribute]
|
||||
- name: Build and publish
|
||||
env:
|
||||
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||
run: |
|
||||
python setup.py sdist bdist_wheel
|
||||
twine upload dist/*
|
||||
create_release:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/create-release@v1
|
||||
id: create_release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: "Moonstream Python client library - ${{ github.ref }}"
|
||||
body: |
|
||||
Version ${{ github.ref }} of the Moonstream Python client library.
|
||||
draft: true
|
||||
prerelease: false
|
|
@ -0,0 +1,38 @@
|
|||
name: Linting and tests for the Moonstream Python client library
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "clients/python/**"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Install test requirements
|
||||
working-directory: ./clients/python
|
||||
run: pip install -e .[dev]
|
||||
- name: Mypy type check
|
||||
working-directory: ./clients/python
|
||||
run: mypy moonstream/
|
||||
- name: Black syntax check
|
||||
working-directory: ./clients/python
|
||||
run: black --check moonstream/
|
||||
- name: Unit tests
|
||||
working-directory: ./clients/python
|
||||
run: python -m unittest discover -v
|
||||
- name: Check that versions are synchronized
|
||||
working-directory: ./clients/python
|
||||
run: |
|
||||
CLIENT_VERSION=$(python -c "from moonstream.client import CLIENT_VERSION; print(CLIENT_VERSION)")
|
||||
SETUP_PY_VERSION=$(python setup.py --version)
|
||||
echo "Client version: $CLIENT_VERSION"
|
||||
echo "setup.py version: $SETUP_PY_VERSION"
|
||||
test "$CLIENT_VERSION" = "$SETUP_PY_VERSION"
|
|
@ -5,6 +5,8 @@ from enum import Enum
|
|||
import uuid
|
||||
|
||||
import boto3 # type: ignore
|
||||
from bugout.data import BugoutSearchResults
|
||||
from bugout.journal import SearchOrder
|
||||
from moonstreamdb.models import (
|
||||
EthereumAddress,
|
||||
EthereumLabel,
|
||||
|
@ -20,12 +22,20 @@ from .settings import (
|
|||
MOONSTREAM_APPLICATION_ID,
|
||||
bugout_client as bc,
|
||||
BUGOUT_REQUEST_TIMEOUT_SECONDS,
|
||||
MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
MOONSTREAM_DATA_JOURNAL_ID,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
ETHERSCAN_SMARTCONTRACT_LABEL_NAME = "etherscan_smartcontract"
|
||||
|
||||
|
||||
class StatusAPIException(Exception):
|
||||
"""
|
||||
Raised during checking Moonstream API statuses.
|
||||
"""
|
||||
|
||||
|
||||
def get_contract_source_info(
|
||||
db_session: Session, contract_address: str
|
||||
) -> Optional[data.EthereumSmartContractSourceInfo]:
|
||||
|
@ -192,3 +202,29 @@ def create_onboarding_resource(
|
|||
timeout=BUGOUT_REQUEST_TIMEOUT_SECONDS,
|
||||
)
|
||||
return resource
|
||||
|
||||
|
||||
def check_api_status():
|
||||
crawl_types_timestamp: Dict[str, Any] = {
|
||||
"ethereum_txpool": None,
|
||||
"ethereum_trending": None,
|
||||
}
|
||||
for crawl_type in crawl_types_timestamp.keys():
|
||||
try:
|
||||
search_results: BugoutSearchResults = bc.search(
|
||||
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
journal_id=MOONSTREAM_DATA_JOURNAL_ID,
|
||||
query=f"tag:crawl_type:{crawl_type}",
|
||||
limit=1,
|
||||
content=False,
|
||||
timeout=10.0,
|
||||
order=SearchOrder.DESCENDING,
|
||||
)
|
||||
if len(search_results.results) == 1:
|
||||
crawl_types_timestamp[crawl_type] = search_results.results[0].created_at
|
||||
except Exception:
|
||||
raise StatusAPIException(
|
||||
f"Unable to get status for crawler with type: {crawl_type}"
|
||||
)
|
||||
|
||||
return crawl_types_timestamp
|
||||
|
|
|
@ -7,7 +7,9 @@ import time
|
|||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from . import actions
|
||||
from . import data
|
||||
from .middleware import MoonstreamHTTPException
|
||||
from .routes.address_info import app as addressinfo_api
|
||||
from .routes.nft import app as nft_api
|
||||
from .routes.subscriptions import app as subscriptions_api
|
||||
|
@ -46,6 +48,27 @@ async def now_handler() -> data.NowResponse:
|
|||
return data.NowResponse(epoch_time=time.time())
|
||||
|
||||
|
||||
@app.get("/status", response_model=data.StatusResponse)
|
||||
async def status_handler() -> data.StatusResponse:
|
||||
"""
|
||||
Get latest records and their creation timestamp for crawlers:
|
||||
- ethereum_txpool
|
||||
- ethereum_trending
|
||||
"""
|
||||
try:
|
||||
crawl_types_timestamp = actions.check_api_status()
|
||||
except actions.StatusAPIException:
|
||||
raise MoonstreamHTTPException(status_code=500)
|
||||
except Exception as e:
|
||||
logger.error(f"Unhandled status exception, error: {e}")
|
||||
raise MoonstreamHTTPException(status_code=500)
|
||||
|
||||
return data.StatusResponse(
|
||||
ethereum_txpool_timestamp=crawl_types_timestamp["ethereum_txpool"],
|
||||
ethereum_trending_timestamp=crawl_types_timestamp["ethereum_trending"],
|
||||
)
|
||||
|
||||
|
||||
app.mount("/subscriptions", subscriptions_api)
|
||||
app.mount("/users", users_api)
|
||||
app.mount("/streams", streams_api)
|
||||
|
|
|
@ -67,6 +67,11 @@ class NowResponse(BaseModel):
|
|||
epoch_time: float
|
||||
|
||||
|
||||
class StatusResponse(BaseModel):
|
||||
ethereum_txpool_timestamp: Optional[datetime] = None
|
||||
ethereum_trending_timestamp: Optional[datetime] = None
|
||||
|
||||
|
||||
class SubscriptionUpdate(BaseModel):
|
||||
update: Dict[str, Any]
|
||||
drop_keys: List[str] = Field(default_factory=list)
|
||||
|
|
|
@ -16,6 +16,7 @@ from sqlalchemy.orm import Session
|
|||
from .. import data
|
||||
from ..stream_queries import StreamQuery
|
||||
|
||||
from ..settings import ETHTXPOOL_HUMBUG_CLIENT_ID
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.WARN)
|
||||
|
@ -376,7 +377,7 @@ ethereum_txpool_provider = EthereumTXPoolProvider(
|
|||
description=ethereum_txpool_description,
|
||||
default_time_interval_seconds=5,
|
||||
estimated_events_per_time_interval=50,
|
||||
tags=["client:ethereum-txpool-crawler-0"],
|
||||
tags=[f"client:{ETHTXPOOL_HUMBUG_CLIENT_ID}"],
|
||||
)
|
||||
|
||||
nft_summary_description = """Event provider for NFT market summaries.
|
||||
|
|
|
@ -7,7 +7,6 @@ import uuid
|
|||
|
||||
from bugout.data import BugoutToken, BugoutUser, BugoutResource, BugoutUserTokens
|
||||
from bugout.exceptions import BugoutResponseException
|
||||
|
||||
from fastapi import (
|
||||
Body,
|
||||
FastAPI,
|
||||
|
|
|
@ -46,6 +46,10 @@ for path in MOONSTREAM_OPENAPI_LIST:
|
|||
|
||||
DEFAULT_STREAM_TIMEINTERVAL = 5 * 60
|
||||
|
||||
ETHTXPOOL_HUMBUG_CLIENT_ID = os.environ.get(
|
||||
"ETHTXPOOL_HUMBUG_CLIENT_ID", "client:ethereum-txpool-crawler-0"
|
||||
)
|
||||
|
||||
# S3 Bucket
|
||||
ETHERSCAN_SMARTCONTRACTS_BUCKET = os.environ.get("AWS_S3_SMARTCONTRACT_BUCKET")
|
||||
if ETHERSCAN_SMARTCONTRACTS_BUCKET is None:
|
||||
|
|
|
@ -9,3 +9,4 @@ export AWS_S3_SMARTCONTRACT_BUCKET="<AWS S3 bucket to store smart contracts>"
|
|||
export BUGOUT_BROOD_URL="https://auth.bugout.dev"
|
||||
export BUGOUT_SPIRE_URL="https://spire.bugout.dev"
|
||||
export HUMBUG_REPORTER_BACKEND_TOKEN="<Bugout Humbug token for crash reports>"
|
||||
export ETHTXPOOL_HUMBUG_CLIENT_ID="<Bugout Humbug client id for txpool transactions in journal>"
|
|
@ -0,0 +1,147 @@
|
|||
# Created by https://www.toptal.com/developers/gitignore/api/python
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=python
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/python
|
||||
|
||||
.moonstream-py/
|
||||
.venv/
|
|
@ -0,0 +1,13 @@
|
|||
# Moonstream Python client
|
||||
|
||||
This is the Python client library for the Moonstream API.
|
||||
|
||||
## Installation
|
||||
|
||||
This library assumes you are using Python 3.6 or greater.
|
||||
|
||||
Install using `pip`:
|
||||
|
||||
```bash
|
||||
pip install moonstream
|
||||
```
|
|
@ -0,0 +1,424 @@
|
|||
from dataclasses import dataclass, field
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import requests
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
log_level = logging.INFO
|
||||
if os.environ.get("DEBUG", "").lower() in ["true", "1"]:
|
||||
log_level = logging.DEBUG
|
||||
logger.setLevel(log_level)
|
||||
|
||||
|
||||
# Keep this synchronized with the version in setup.py
|
||||
CLIENT_VERSION = "0.0.2"
|
||||
|
||||
ENDPOINT_PING = "/ping"
|
||||
ENDPOINT_VERSION = "/version"
|
||||
ENDPOINT_NOW = "/now"
|
||||
ENDPOINT_TOKEN = "/users/token"
|
||||
ENDPOINT_SUBSCRIPTIONS = "/subscriptions/"
|
||||
ENDPOINT_SUBSCRIPTION_TYPES = "/subscriptions/types"
|
||||
ENDPOINT_STREAMS = "/streams/"
|
||||
ENDPOINT_STREAMS_LATEST = "/streams/latest"
|
||||
ENDPOINT_STREAMS_NEXT = "/streams/next"
|
||||
ENDPOINT_STREAMS_PREVIOUS = "/streams/previous"
|
||||
|
||||
ENDPOINTS = [
|
||||
ENDPOINT_PING,
|
||||
ENDPOINT_VERSION,
|
||||
ENDPOINT_NOW,
|
||||
ENDPOINT_TOKEN,
|
||||
ENDPOINT_SUBSCRIPTIONS,
|
||||
ENDPOINT_SUBSCRIPTION_TYPES,
|
||||
ENDPOINT_STREAMS,
|
||||
ENDPOINT_STREAMS_LATEST,
|
||||
ENDPOINT_STREAMS_NEXT,
|
||||
ENDPOINT_STREAMS_PREVIOUS,
|
||||
]
|
||||
|
||||
|
||||
def moonstream_endpoints(url: str) -> Dict[str, str]:
|
||||
"""
|
||||
Creates a dictionary of Moonstream API endpoints at the given Moonstream API URL.
|
||||
"""
|
||||
url_with_protocol = url
|
||||
if not (
|
||||
url_with_protocol.startswith("http://")
|
||||
or url_with_protocol.startswith("https://")
|
||||
):
|
||||
url_with_protocol = f"http://{url_with_protocol}"
|
||||
|
||||
normalized_url = url_with_protocol.rstrip("/")
|
||||
|
||||
return {endpoint: f"{normalized_url}{endpoint}" for endpoint in ENDPOINTS}
|
||||
|
||||
|
||||
class UnexpectedResponse(Exception):
|
||||
"""
|
||||
Raised when a server response cannot be parsed into the appropriate/expected Python structure.
|
||||
"""
|
||||
|
||||
|
||||
class Unauthenticated(Exception):
|
||||
"""
|
||||
Raised when a user tries to make a request that needs to be authenticated by they are not authenticated.
|
||||
"""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class APISpec:
|
||||
url: str
|
||||
endpoints: Dict[str, str]
|
||||
|
||||
|
||||
class Moonstream:
|
||||
"""
|
||||
A Moonstream client configured to communicate with a given Moonstream API server.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
url: str = "https://api.moonstream.to",
|
||||
timeout: Optional[float] = None,
|
||||
):
|
||||
"""
|
||||
Initializes a Moonstream API client.
|
||||
|
||||
Arguments:
|
||||
url - Moonstream API URL. By default this points to the production Moonstream API at https://api.moonstream.to,
|
||||
but you can replace it with the URL of any other Moonstream API instance.
|
||||
timeout - Timeout (in seconds) for Moonstream API requests. Default is None, which means that
|
||||
Moonstream API requests will never time out.
|
||||
|
||||
Returns: A Moonstream client.
|
||||
"""
|
||||
endpoints = moonstream_endpoints(url)
|
||||
self.api = APISpec(url=url, endpoints=endpoints)
|
||||
self.timeout = timeout
|
||||
self._session = requests.Session()
|
||||
self._session.headers.update(
|
||||
{"User-Agent": f"Moonstream Python client (version {CLIENT_VERSION})"}
|
||||
)
|
||||
|
||||
def ping(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Checks that you have a connection to the Moonstream API.
|
||||
"""
|
||||
r = self._session.get(self.api.endpoints[ENDPOINT_PING])
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def version(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Gets the Moonstream API version information from the server.
|
||||
"""
|
||||
r = self._session.get(self.api.endpoints[ENDPOINT_VERSION])
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def server_time(self) -> float:
|
||||
"""
|
||||
Gets the current time (as microseconds since the Unix epoch) on the server.
|
||||
"""
|
||||
r = self._session.get(self.api.endpoints[ENDPOINT_NOW])
|
||||
r.raise_for_status()
|
||||
result = r.json()
|
||||
raw_epoch_time = result.get("epoch_time")
|
||||
if raw_epoch_time is None:
|
||||
raise UnexpectedResponse(
|
||||
f'Server response does not contain "epoch_time": {result}'
|
||||
)
|
||||
|
||||
try:
|
||||
epoch_time = float(raw_epoch_time)
|
||||
except:
|
||||
raise UnexpectedResponse(
|
||||
f"Could not process epoch time as a float: {raw_epoch_time}"
|
||||
)
|
||||
|
||||
return epoch_time
|
||||
|
||||
def authorize(self, access_token: str) -> None:
|
||||
if not access_token:
|
||||
logger.warning("Setting authorization header to empty token.")
|
||||
self._session.headers.update({"Authorization": f"Bearer {access_token}"})
|
||||
|
||||
def requires_authorization(self):
|
||||
if self._session.headers.get("Authorization") is None:
|
||||
raise Unauthenticated(
|
||||
'This method requires that you authenticate to the API, either by calling the "authorize" method with an API token or by calling the "login" method.'
|
||||
)
|
||||
|
||||
def login(self, username: str, password: Optional[str] = None) -> str:
|
||||
"""
|
||||
Authorizes this client to act as the given user when communicating with the Moonstream API.
|
||||
|
||||
To register an account on the production Moonstream API, go to https://moonstream.to.
|
||||
|
||||
Arguments:
|
||||
username - Username of the user to authenticate as.
|
||||
password - Optional password for the user. If this is not provided, you will be prompted for
|
||||
the password.
|
||||
"""
|
||||
if password is None:
|
||||
password = input(f"Moonstream password for {username}: ")
|
||||
|
||||
r = self._session.post(
|
||||
self.api.endpoints[ENDPOINT_TOKEN],
|
||||
data={"username": username, "password": password},
|
||||
)
|
||||
r.raise_for_status()
|
||||
|
||||
token = r.json()
|
||||
self.authorize(token["id"])
|
||||
return token
|
||||
|
||||
def logout(self) -> None:
|
||||
"""
|
||||
Logs the current user out of the Moonstream client.
|
||||
"""
|
||||
self._session.delete(self.api.endpoints[ENDPOINT_TOKEN])
|
||||
self._session.headers.pop("Authorization")
|
||||
|
||||
def subscription_types(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Gets the currently available subscription types on the Moonstream API.
|
||||
"""
|
||||
r = self._session.get(self.api.endpoints[ENDPOINT_SUBSCRIPTION_TYPES])
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def list_subscriptions(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Gets the currently authorized user's subscriptions from the API server.
|
||||
"""
|
||||
self.requires_authorization()
|
||||
r = self._session.get(self.api.endpoints[ENDPOINT_SUBSCRIPTIONS])
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def create_subscription(
|
||||
self, subscription_type: str, label: str, color: str, specifier: str = ""
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Creates a subscription.
|
||||
|
||||
Arguments:
|
||||
subscription_type - The type of subscription you would like to create. To see the available subscription
|
||||
types, call the "subscription_types" method on this Moonstream client. This argument must be
|
||||
the "id" if the subscription type you want.
|
||||
label - A label for the subscription. This will identify the subscription to you in your stream.
|
||||
color - A hexadecimal color to associate with the subscription.
|
||||
specifier - A specifier for the subscription, which must correspond to one of the choices in the
|
||||
subscription type. This is optional because some subscription types do not require a specifier.
|
||||
|
||||
Returns: The subscription resource that was created on the backend.
|
||||
"""
|
||||
self.requires_authorization()
|
||||
r = self._session.post(
|
||||
self.api.endpoints[ENDPOINT_SUBSCRIPTIONS],
|
||||
data={
|
||||
"subscription_type_id": subscription_type,
|
||||
"label": label,
|
||||
"color": color,
|
||||
"address": specifier,
|
||||
},
|
||||
)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def delete_subscription(self, id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Delete a subscription by ID.
|
||||
|
||||
Arguments:
|
||||
id - ID of the subscription to delete.
|
||||
|
||||
Returns: The subscription resource that was deleted.
|
||||
"""
|
||||
self.requires_authorization()
|
||||
r = self._session.delete(f"{self.api.endpoints[ENDPOINT_SUBSCRIPTIONS]}{id}")
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def update_subscription(
|
||||
self, id: str, label: Optional[str] = None, color: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Update a subscription label or color.
|
||||
|
||||
Arguments:
|
||||
label - New label for subscription (optional).
|
||||
color - New color for subscription (optional).
|
||||
|
||||
Returns - If neither label or color are specified, raises a ValueError. Otherwise PUTs the updated
|
||||
information to the server and returns the updated subscription resource.
|
||||
"""
|
||||
if label is None and color is None:
|
||||
raise ValueError(
|
||||
"At least one of the arguments to this method should not be None."
|
||||
)
|
||||
self.requires_authorization()
|
||||
data = {}
|
||||
if label is not None:
|
||||
data["label"] = label
|
||||
if color is not None:
|
||||
data["color"] = color
|
||||
|
||||
r = self._session.put(
|
||||
f"{self.api.endpoints[ENDPOINT_SUBSCRIPTIONS]}{id}", data=data
|
||||
)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def latest_events(self, q: str = "") -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Returns the latest events in your stream. You can optionally provide a query parameter to
|
||||
constrain the query to specific subscription types or to specific subscriptions.
|
||||
|
||||
Arguments:
|
||||
- q - Optional query (default is the empty string). The syntax to constrain to a particular
|
||||
type of subscription is "type:<subscription_type>". For example, to get the latest event from
|
||||
your Ethereum transaction pool subscriptions, you would use "type:ethereum_txpool".
|
||||
|
||||
Returns: A list of the latest events in your stream.
|
||||
"""
|
||||
self.requires_authorization()
|
||||
query_params: Dict[str, str] = {}
|
||||
if q:
|
||||
query_params["q"] = q
|
||||
r = self._session.get(
|
||||
self.api.endpoints[ENDPOINT_STREAMS_LATEST], params=query_params
|
||||
)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def next_event(
|
||||
self, end_time: int, include_end: bool = True, q: str = ""
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Return the earliest event in your stream that occurred after the given end_time.
|
||||
|
||||
Arguments:
|
||||
- end_time - Time after which you want to retrieve the earliest event from your stream.
|
||||
- include_end - If True, the result is the first event that occurred in your stream strictly
|
||||
*after* the end time. If False, then you will get the first event that occurred in your
|
||||
stream *on* or *after* the end time.
|
||||
- q - Optional query to filter over your available subscriptions and subscription types.
|
||||
|
||||
Returns: None if no event has occurred after the given end time, else returns a dictionary
|
||||
representing that event.
|
||||
"""
|
||||
self.requires_authorization()
|
||||
query_params: Dict[str, Any] = {
|
||||
"end_time": end_time,
|
||||
"include_end": include_end,
|
||||
}
|
||||
if q:
|
||||
query_params["q"] = q
|
||||
r = self._session.get(
|
||||
self.api.endpoints[ENDPOINT_STREAMS_NEXT], params=query_params
|
||||
)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def previous_event(
|
||||
self, start_time: int, include_start: bool = True, q: str = ""
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Return the latest event in your stream that occurred before the given start_time.
|
||||
|
||||
Arguments:
|
||||
- start_time - Time before which you want to retrieve the latest event from your stream.
|
||||
- include_start - If True, the result is the last event that occurred in your stream strictly
|
||||
*before* the start time. If False, then you will get the last event that occurred in your
|
||||
stream *on* or *before* the start time.
|
||||
- q - Optional query to filter over your available subscriptions and subscription types.
|
||||
|
||||
Returns: None if no event has occurred before the given start time, else returns a dictionary
|
||||
representing that event.
|
||||
"""
|
||||
self.requires_authorization()
|
||||
query_params: Dict[str, Any] = {
|
||||
"start_time": start_time,
|
||||
"include_start": include_start,
|
||||
}
|
||||
if q:
|
||||
query_params["q"] = q
|
||||
r = self._session.get(
|
||||
self.api.endpoints[ENDPOINT_STREAMS_PREVIOUS], params=query_params
|
||||
)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def events(
|
||||
self,
|
||||
start_time: int,
|
||||
end_time: int,
|
||||
include_start: bool = False,
|
||||
include_end: bool = False,
|
||||
q: str = "",
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Return all events in your stream that occurred between the given start and end times.
|
||||
|
||||
Arguments:
|
||||
- start_time - Time after which you want to query your stream.
|
||||
- include_start - Whether or not events that occurred exactly at the start_time should be included in the results.
|
||||
- end_time - Time before which you want to query your stream.
|
||||
- include_end - Whether or not events that occurred exactly at the end_time should be included in the results.
|
||||
- q - Optional query to filter over your available subscriptions and subscription types.
|
||||
|
||||
Returns: A dictionary representing the results of your query.
|
||||
"""
|
||||
self.requires_authorization()
|
||||
query_params: Dict[str, Any] = {
|
||||
"start_time": start_time,
|
||||
"include_start": include_start,
|
||||
"end_time": end_time,
|
||||
"include_end": include_end,
|
||||
}
|
||||
if q:
|
||||
query_params["q"] = q
|
||||
|
||||
r = self._session.get(self.api.endpoints[ENDPOINT_STREAMS], params=query_params)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
|
||||
def client_from_env() -> Moonstream:
|
||||
"""
|
||||
Produces a Moonstream client instantiated using the following environment variables:
|
||||
- MOONSTREAM_API_URL: Specifies the url parameter on the Moonstream client
|
||||
- MOONSTREAM_TIMEOUT_SECONDS: Specifies the request timeout
|
||||
- MOONSTREAM_ACCESS_TOKEN: If this environment variable is defined, the client sets this token as
|
||||
the authorization header for all Moonstream API requests.
|
||||
"""
|
||||
kwargs: Dict[str, Any] = {}
|
||||
|
||||
url = os.environ.get("MOONSTREAM_API_URL")
|
||||
if url is not None:
|
||||
kwargs["url"] = url
|
||||
|
||||
raw_timeout = os.environ.get("MOONSTREAM_TIMEOUT_SECONDS")
|
||||
timeout: Optional[float] = None
|
||||
if raw_timeout is not None:
|
||||
try:
|
||||
timeout = float(raw_timeout)
|
||||
except:
|
||||
raise ValueError(
|
||||
f"Could not convert MOONSTREAM_TIMEOUT_SECONDS ({raw_timeout}) to float."
|
||||
)
|
||||
|
||||
kwargs["timeout"] = timeout
|
||||
|
||||
moonstream_client = Moonstream(**kwargs)
|
||||
|
||||
access_token = os.environ.get("MOONSTREAM_ACCESS_TOKEN")
|
||||
if access_token is not None:
|
||||
moonstream_client.authorize(access_token)
|
||||
|
||||
return moonstream_client
|
|
@ -0,0 +1,138 @@
|
|||
from dataclasses import FrozenInstanceError
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from . import client
|
||||
|
||||
|
||||
class TestMoonstreamClient(unittest.TestCase):
|
||||
def test_client_init(self):
|
||||
m = client.Moonstream()
|
||||
self.assertEqual(m.api.url, "https://api.moonstream.to")
|
||||
self.assertIsNone(m.timeout)
|
||||
self.assertGreater(len(m.api.endpoints), 0)
|
||||
|
||||
def test_client_init_with_timeout(self):
|
||||
timeout = 7
|
||||
m = client.Moonstream(timeout=timeout)
|
||||
self.assertEqual(m.api.url, "https://api.moonstream.to")
|
||||
self.assertEqual(m.timeout, timeout)
|
||||
self.assertGreater(len(m.api.endpoints), 0)
|
||||
|
||||
def test_client_with_custom_url_and_timeout(self):
|
||||
timeout = 9
|
||||
url = "https://my.custom.api.url"
|
||||
m = client.Moonstream(url=url, timeout=timeout)
|
||||
self.assertEqual(m.api.url, url)
|
||||
self.assertEqual(m.timeout, timeout)
|
||||
self.assertGreater(len(m.api.endpoints), 0)
|
||||
|
||||
def test_client_with_custom_messy_url_and_timeout(self):
|
||||
timeout = 3.5
|
||||
url = "https://my.custom.api.url/"
|
||||
m = client.Moonstream(url=url, timeout=timeout)
|
||||
self.assertEqual(m.api.url, url)
|
||||
self.assertEqual(m.timeout, timeout)
|
||||
self.assertGreater(len(m.api.endpoints), 0)
|
||||
|
||||
def test_client_with_custom_messy_url_no_protocol_and_timeout(self):
|
||||
timeout = 5.5
|
||||
url = "my.custom.api.url/"
|
||||
m = client.Moonstream(url=url, timeout=timeout)
|
||||
self.assertEqual(m.api.url, url)
|
||||
self.assertEqual(m.timeout, timeout)
|
||||
self.assertGreater(len(m.api.endpoints), 0)
|
||||
|
||||
def test_immutable_api_url(self):
|
||||
m = client.Moonstream()
|
||||
with self.assertRaises(FrozenInstanceError):
|
||||
m.api.url = "lol"
|
||||
|
||||
def test_immutable_api_endpoints(self):
|
||||
m = client.Moonstream()
|
||||
with self.assertRaises(FrozenInstanceError):
|
||||
m.api.endpoints = {}
|
||||
|
||||
def test_mutable_timeout(self):
|
||||
original_timeout = 5.0
|
||||
updated_timeout = 10.5
|
||||
m = client.Moonstream(timeout=original_timeout)
|
||||
self.assertEqual(m.timeout, original_timeout)
|
||||
m.timeout = updated_timeout
|
||||
self.assertEqual(m.timeout, updated_timeout)
|
||||
|
||||
|
||||
class TestMoonstreamClientFromEnv(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.old_moonstream_api_url = os.environ.get("MOONSTREAM_API_URL")
|
||||
self.old_moonstream_timeout_seconds = os.environ.get(
|
||||
"MOONSTREAM_TIMEOUT_SECONDS"
|
||||
)
|
||||
self.old_moonstream_access_token = os.environ.get("MOONSTREAM_ACCESS_TOKEN")
|
||||
|
||||
self.moonstream_api_url = "https://custom.example.com"
|
||||
self.moonstream_timeout_seconds = 15.333333
|
||||
self.moonstream_access_token = "1d431ca4-af9b-4c3a-b7b9-3cc79f3b0900"
|
||||
|
||||
os.environ["MOONSTREAM_API_URL"] = self.moonstream_api_url
|
||||
os.environ["MOONSTREAM_TIMEOUT_SECONDS"] = str(self.moonstream_timeout_seconds)
|
||||
os.environ["MOONSTREAM_ACCESS_TOKEN"] = self.moonstream_access_token
|
||||
|
||||
def tearDown(self) -> None:
|
||||
del os.environ["MOONSTREAM_API_URL"]
|
||||
del os.environ["MOONSTREAM_TIMEOUT_SECONDS"]
|
||||
del os.environ["MOONSTREAM_ACCESS_TOKEN"]
|
||||
|
||||
if self.old_moonstream_api_url is not None:
|
||||
os.environ["MOONSTREAM_API_URL"] = self.old_moonstream_api_url
|
||||
if self.old_moonstream_timeout_seconds is not None:
|
||||
os.environ[
|
||||
"MOONSTREAM_TIMEOUT_SECONDS"
|
||||
] = self.old_moonstream_timeout_seconds
|
||||
if self.old_moonstream_access_token is not None:
|
||||
os.environ["MOONSTREAM_ACCESS_TOKEN"] = self.old_moonstream_access_token
|
||||
|
||||
def test_client_from_env(self):
|
||||
m = client.client_from_env()
|
||||
self.assertEqual(m.api.url, self.moonstream_api_url)
|
||||
self.assertEqual(m.timeout, self.moonstream_timeout_seconds)
|
||||
self.assertIsNone(m.requires_authorization())
|
||||
|
||||
authorization_header = m._session.headers["Authorization"]
|
||||
self.assertEqual(authorization_header, f"Bearer {self.moonstream_access_token}")
|
||||
|
||||
|
||||
class TestMoonstreamEndpoints(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.url = "https://api.moonstream.to"
|
||||
self.normalized_url = "https://api.moonstream.to"
|
||||
|
||||
def test_moonstream_endpoints(self):
|
||||
endpoints = client.moonstream_endpoints(self.url)
|
||||
self.assertDictEqual(
|
||||
endpoints,
|
||||
{
|
||||
client.ENDPOINT_PING: f"{self.normalized_url}{client.ENDPOINT_PING}",
|
||||
client.ENDPOINT_VERSION: f"{self.normalized_url}{client.ENDPOINT_VERSION}",
|
||||
client.ENDPOINT_NOW: f"{self.normalized_url}{client.ENDPOINT_NOW}",
|
||||
client.ENDPOINT_TOKEN: f"{self.normalized_url}{client.ENDPOINT_TOKEN}",
|
||||
client.ENDPOINT_SUBSCRIPTION_TYPES: f"{self.normalized_url}{client.ENDPOINT_SUBSCRIPTION_TYPES}",
|
||||
client.ENDPOINT_SUBSCRIPTIONS: f"{self.normalized_url}{client.ENDPOINT_SUBSCRIPTIONS}",
|
||||
client.ENDPOINT_STREAMS: f"{self.normalized_url}{client.ENDPOINT_STREAMS}",
|
||||
client.ENDPOINT_STREAMS_LATEST: f"{self.normalized_url}{client.ENDPOINT_STREAMS_LATEST}",
|
||||
client.ENDPOINT_STREAMS_NEXT: f"{self.normalized_url}{client.ENDPOINT_STREAMS_NEXT}",
|
||||
client.ENDPOINT_STREAMS_PREVIOUS: f"{self.normalized_url}{client.ENDPOINT_STREAMS_PREVIOUS}",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class TestMoonstreamEndpointsMessyURL(TestMoonstreamEndpoints):
|
||||
def setUp(self):
|
||||
self.url = "https://api.moonstream.to/"
|
||||
self.normalized_url = "https://api.moonstream.to"
|
||||
|
||||
|
||||
class TestMoonstreamEndpointsMessyURLWithNoProtocol(TestMoonstreamEndpoints):
|
||||
def setUp(self):
|
||||
self.url = "api.moonstream.to/"
|
||||
self.normalized_url = "http://api.moonstream.to"
|
|
@ -0,0 +1,35 @@
|
|||
from setuptools import find_packages, setup
|
||||
|
||||
long_description = ""
|
||||
with open("README.md") as ifp:
|
||||
long_description = ifp.read()
|
||||
|
||||
setup(
|
||||
name="moonstream",
|
||||
version="0.0.2",
|
||||
packages=find_packages(),
|
||||
package_data={"moonstream": ["py.typed"]},
|
||||
install_requires=["requests", "dataclasses; python_version=='3.6'"],
|
||||
extras_require={
|
||||
"dev": [
|
||||
"black",
|
||||
"mypy",
|
||||
"wheel",
|
||||
"types-requests",
|
||||
"types-dataclasses",
|
||||
],
|
||||
"distribute": ["setuptools", "twine", "wheel"],
|
||||
},
|
||||
description="Moonstream: Open source blockchain analytics",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
author="Moonstream",
|
||||
author_email="engineering@moonstream.to",
|
||||
classifiers=[
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Programming Language :: Python",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
],
|
||||
url="https://github.com/bugout-dev/moonstream",
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
TAG="clients/python/v$(python setup.py --version)"
|
||||
read -r -p "Tag: $TAG -- tag and push (y/n)?" ACCEPT
|
||||
if [ "$ACCEPT" = "y" ]
|
||||
then
|
||||
echo "Tagging and pushing: $TAG..."
|
||||
git tag "$TAG"
|
||||
git push upstream "$TAG"
|
||||
else
|
||||
echo "noop"
|
||||
fi
|
|
@ -19,6 +19,7 @@ ETHEREUM_SYNCHRONIZE_SERVICE="ethereum-synchronize.service"
|
|||
ETHEREUM_TRENDING_SERVICE="ethereum-trending.service"
|
||||
ETHEREUM_TRENDING_TIMER="ethereum-trending.service"
|
||||
ETHEREUM_TXPOOL_SERVICE="ethereum-txpool.service"
|
||||
SERVICE_FILE="moonstreamcrawlers.service"
|
||||
|
||||
set -eu
|
||||
|
||||
|
@ -30,6 +31,14 @@ cd "${APP_CRAWLERS_DIR}/ethtxpool"
|
|||
HOME=/root /usr/local/go/bin/go build -o "${APP_CRAWLERS_DIR}/ethtxpool/ethtxpool" "${APP_CRAWLERS_DIR}/ethtxpool/main.go"
|
||||
cd "${EXEC_DIR}"
|
||||
|
||||
echo
|
||||
echo
|
||||
echo "Building executable server of moonstreamcrawlers with Go"
|
||||
EXEC_DIR=$(pwd)
|
||||
cd "${APP_CRAWLERS_DIR}/server"
|
||||
HOME=/root /usr/local/go/bin/go build -o "${APP_CRAWLERS_DIR}/server/moonstreamcrawlers" "${APP_CRAWLERS_DIR}/server/main.go"
|
||||
cd "${EXEC_DIR}"
|
||||
|
||||
echo
|
||||
echo
|
||||
echo "Updating Python dependencies"
|
||||
|
@ -82,3 +91,12 @@ chmod 644 "${SCRIPT_DIR}/${ETHEREUM_TXPOOL_SERVICE}"
|
|||
cp "${SCRIPT_DIR}/${ETHEREUM_TXPOOL_SERVICE}" "/etc/systemd/system/${ETHEREUM_TXPOOL_SERVICE}"
|
||||
systemctl daemon-reload
|
||||
systemctl restart "${ETHEREUM_TXPOOL_SERVICE}"
|
||||
|
||||
echo
|
||||
echo
|
||||
echo "Replacing existing moonstreamcrawlers service definition with ${SERVICE_FILE}"
|
||||
chmod 644 "${SCRIPT_DIR}/${SERVICE_FILE}"
|
||||
cp "${SCRIPT_DIR}/${SERVICE_FILE}" "/etc/systemd/system/${SERVICE_FILE}"
|
||||
systemctl daemon-reload
|
||||
systemctl restart "${SERVICE_FILE}"
|
||||
systemctl status "${SERVICE_FILE}"
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=moonstreamcrawlers-service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=ubuntu
|
||||
Group=www-data
|
||||
WorkingDirectory=/home/ubuntu/moonstream/crawlers/server
|
||||
EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env
|
||||
ExecStart=/home/ubuntu/moonstream/crawlers/server/moonstreamcrawlers -host 0.0.0.0 -port "${MOONSTREAM_CRAWLERS_SERVER_PORT}"
|
||||
SyslogIdentifier=moonstreamcrawlers
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Expects access to Python environment with the requirements for this project installed.
|
||||
set -e
|
||||
|
||||
MOONSTREAM_CRAWLERS_SERVER_HOST="${MOONSTREAM_CRAWLERS_SERVER_HOST:-0.0.0.0}"
|
||||
MOONSTREAM_CRAWLERS_SERVER_PORT="${MOONSTREAM_CRAWLERS_SERVER_PORT:-8080}"
|
||||
|
||||
go run main.go -host "${MOONSTREAM_CRAWLERS_SERVER_HOST}" -port "${MOONSTREAM_CRAWLERS_SERVER_PORT}"
|
|
@ -0,0 +1,3 @@
|
|||
module moonstreamdb
|
||||
|
||||
go 1.17
|
|
@ -0,0 +1,96 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var MOONSTREAM_IPC_PATH = os.Getenv("MOONSTREAM_IPC_PATH")
|
||||
|
||||
type GethResponse struct {
|
||||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
type PingGethResponse struct {
|
||||
CurrentBlock uint64 `json:"current_block"`
|
||||
}
|
||||
|
||||
type PingResponse struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func ping(w http.ResponseWriter, req *http.Request) {
|
||||
log.Printf("%s, %s, %q", req.RemoteAddr, req.Method, req.URL.String())
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
response := PingResponse{Status: "ok"}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func pingGeth(w http.ResponseWriter, req *http.Request) {
|
||||
log.Printf("%s, %s, %q", req.RemoteAddr, req.Method, req.URL.String())
|
||||
|
||||
postBody, err := json.Marshal(map[string]interface{}{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "eth_blockNumber",
|
||||
"params": []string{},
|
||||
"id": 1,
|
||||
})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, http.StatusText(500), 500)
|
||||
return
|
||||
}
|
||||
gethResponse, err := http.Post(MOONSTREAM_IPC_PATH, "application/json",
|
||||
bytes.NewBuffer(postBody))
|
||||
if err != nil {
|
||||
log.Printf("Unable to request geth, error: %v", err)
|
||||
http.Error(w, http.StatusText(500), 500)
|
||||
return
|
||||
}
|
||||
defer gethResponse.Body.Close()
|
||||
|
||||
gethResponseBody, err := ioutil.ReadAll(gethResponse.Body)
|
||||
if err != nil {
|
||||
log.Printf("Unable to read geth response, error: %v", err)
|
||||
http.Error(w, http.StatusText(500), 500)
|
||||
return
|
||||
}
|
||||
var obj GethResponse
|
||||
_ = json.Unmarshal(gethResponseBody, &obj)
|
||||
|
||||
blockNumberHex := strings.Replace(obj.Result, "0x", "", -1)
|
||||
blockNumberStr, err := strconv.ParseUint(blockNumberHex, 16, 64)
|
||||
if err != nil {
|
||||
log.Printf("Unable to parse block number from hex to string, error: %v", err)
|
||||
http.Error(w, http.StatusText(500), 500)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
response := PingGethResponse{CurrentBlock: blockNumberStr}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func main() {
|
||||
var listenAddr string
|
||||
var listenPort string
|
||||
flag.StringVar(&listenAddr, "host", "127.0.0.1", "Server listen address")
|
||||
flag.StringVar(&listenPort, "port", "8080", "Server listen port")
|
||||
flag.Parse()
|
||||
|
||||
address := listenAddr + ":" + listenPort
|
||||
log.Printf("Starting server at %s\n", address)
|
||||
|
||||
http.HandleFunc("/ping", ping)
|
||||
http.HandleFunc("/status", pingGeth)
|
||||
|
||||
http.ListenAndServe(address, nil)
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export MOONSTREAM_CRAWLERS_SERVER_PORT="8080"
|
||||
export MOONSTREAM_IPC_PATH=null
|
|
@ -5,15 +5,32 @@
|
|||
# Main
|
||||
APP_DIR="${APP_DIR:-/home/ubuntu/moonstream}"
|
||||
APP_DB_SERVER_DIR="${APP_DIR}/db/server"
|
||||
SECRETS_DIR="${SECRETS_DIR:-/home/ubuntu/moonstream-secrets}"
|
||||
SCRIPT_DIR="$(realpath $(dirname $0))"
|
||||
SERVICE_FILE="${SCRIPT_DIR}/moonstreamdb.service"
|
||||
|
||||
set -eu
|
||||
|
||||
echo
|
||||
echo
|
||||
echo "Retrieving deployment parameters for GCL Secret Manager"
|
||||
mkdir -p "${SECRETS_DIR}"
|
||||
echo "" > "${SECRETS_DIR}/app.env"
|
||||
SECRET_NAMES=$(gcloud beta secrets list --filter="labels.product=moonstream" --format="get(name)")
|
||||
for secret in $SECRET_NAMES
|
||||
do
|
||||
secret_key=$(echo "${secret}" | awk -F'/' '{print $NF}')
|
||||
secret_val=$(gcloud secrets versions access latest --secret="${secret_key}")
|
||||
echo "${secret_key}=\"${secret_val}\"" >> "${SECRETS_DIR}/app.env"
|
||||
done
|
||||
|
||||
echo
|
||||
echo
|
||||
echo "Building executable database server script with Go"
|
||||
EXEC_DIR=$(pwd)
|
||||
cd "${APP_DB_SERVER_DIR}"
|
||||
HOME=/root /usr/local/go/bin/go build -o "${APP_DB_SERVER_DIR}/moonstreamdb" "${APP_DB_SERVER_DIR}/main.go"
|
||||
cd "${EXEC_DIR}"
|
||||
|
||||
echo
|
||||
echo
|
||||
|
|
|
@ -6,7 +6,8 @@ After=network.target
|
|||
User=ubuntu
|
||||
Group=www-data
|
||||
WorkingDirectory=/home/ubuntu/moonstream/db/server
|
||||
ExecStart=/home/ubuntu/moonstream/db/server/moonstreamdb
|
||||
EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env
|
||||
ExecStart=/home/ubuntu/moonstream/db/server/moonstreamdb -host 0.0.0.0 -port "${MOONSTREAM_DB_SERVER_PORT}"
|
||||
SyslogIdentifier=moonstreamdb
|
||||
|
||||
[Install]
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Expects access to Python environment with the requirements for this project installed.
|
||||
set -e
|
||||
|
||||
MOONSTREAM_DB_SERVER_HOST="${MOONSTREAM_DB_SERVER_HOST:-0.0.0.0}"
|
||||
MOONSTREAM_DB_SERVER_PORT="${MOONSTREAM_DB_SERVER_PORT:-8080}"
|
||||
|
||||
go run main.go -host "${MOONSTREAM_DB_SERVER_HOST}" -port "${MOONSTREAM_DB_SERVER_PORT}"
|
|
@ -1,3 +1,23 @@
|
|||
module moonstreamdb
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
gorm.io/driver/postgres v1.1.1
|
||||
gorm.io/gorm v1.21.15
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.10.0 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.1.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.8.1 // indirect
|
||||
github.com/jackc/pgx/v4 v4.13.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
)
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgconn v1.10.0 h1:4EYhlDVEMsJ30nNj0mmgwIUXoq7e9sMJrVC2ED6QlCU=
|
||||
github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
|
||||
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI=
|
||||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||
github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs=
|
||||
github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||
github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570=
|
||||
github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
|
||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.1.1 h1:tWLmqYCyaoh89fi7DhM6QggujrOnmfo3H98AzgNAAu0=
|
||||
gorm.io/driver/postgres v1.1.1/go.mod h1:tpe2xN7aCst1NUdYyWQyxPtnHC+Zfp6NEux9PXD1OU0=
|
||||
gorm.io/gorm v1.21.15 h1:gAyaDoPw0lCyrSFWhBlahbUA1U4P5RViC1uIqoB+1Rk=
|
||||
gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
|
@ -2,25 +2,63 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var MOONSTREAM_DB_URI = os.Getenv("MOONSTREAM_DB_URI")
|
||||
|
||||
type PingResponse struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type BlockResponse struct {
|
||||
BlockNumber uint64 `json:"block_number"`
|
||||
}
|
||||
|
||||
func ping(w http.ResponseWriter, req *http.Request) {
|
||||
log.Printf("%s, %s, %q", req.RemoteAddr, req.Method, req.URL.String())
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
response := PingResponse{Status: "ok"}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func blockLatest(w http.ResponseWriter, req *http.Request) {
|
||||
log.Printf("%s, %s, %q", req.RemoteAddr, req.Method, req.URL.String())
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
var latestBlock BlockResponse
|
||||
db, err := gorm.Open(postgres.Open(MOONSTREAM_DB_URI), &gorm.Config{})
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(500), 500)
|
||||
return
|
||||
}
|
||||
|
||||
query := "SELECT block_number FROM ethereum_blocks ORDER BY block_number DESC LIMIT 1"
|
||||
db.Raw(query, 1).Scan(&latestBlock.BlockNumber)
|
||||
|
||||
json.NewEncoder(w).Encode(latestBlock)
|
||||
}
|
||||
|
||||
func main() {
|
||||
address := "0.0.0.0:8931"
|
||||
fmt.Printf("Starting server at %s\n", address)
|
||||
var listenAddr string
|
||||
var listenPort string
|
||||
flag.StringVar(&listenAddr, "host", "127.0.0.1", "Server listen address")
|
||||
flag.StringVar(&listenPort, "port", "8080", "Server listen port")
|
||||
flag.Parse()
|
||||
|
||||
address := listenAddr + ":" + listenPort
|
||||
log.Printf("Starting server at %s\n", address)
|
||||
|
||||
http.HandleFunc("/ping", ping)
|
||||
http.HandleFunc("/block/latest", blockLatest)
|
||||
|
||||
http.ListenAndServe(address, nil)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export MOONSTREAM_DB_SERVER_PORT="8080"
|
||||
export MOONSTREAM_DB_URI="postgresql://<username>:<password>@<db_host>:<db_port>/<db_name>"
|
|
@ -59,7 +59,11 @@ const RootLayout = (props) => {
|
|||
>
|
||||
Join early. Our first 1000 users get free lifetime access to
|
||||
blockchain analytics. Contact our team on{" "}
|
||||
<Link href={"https://discord.gg/V3tWaP36"} color="orange.900">
|
||||
<Link
|
||||
isExternal
|
||||
href={"https://discord.gg/K56VNUQGvA"}
|
||||
color="orange.900"
|
||||
>
|
||||
Discord
|
||||
</Link>
|
||||
</Text>
|
||||
|
|
Ładowanie…
Reference in New Issue