Felix expressing ideas on how to wait for chain sync using bootnodes

pull/1040/head
Kieran Prasch 2019-04-29 23:47:00 +03:00
parent 0b304ec560
commit 8407f22ebf
No known key found for this signature in database
GPG Key ID: 199AB839D4125A62
5 changed files with 120 additions and 14 deletions

7
deploy/static-nodes.json Normal file
View File

@ -0,0 +1,7 @@
[
"enode://bf150c793f378775e8cf09bee4fba37ea65363fe7a41171790a80ef6462de619cad2c05f42fc58655ad317503d5da8fee898e911fdf386ac6d15da12b5e883eb@3.92.166.78:30301",
"enode://13da3c4b5b1ca32dfb0fcd662b9c69daf6b564e6f791ddae107d57049f25952aac329de336fd393f5b42b6aa2bbb263d7aa5c426b473be611739795aa18b0212@54.173.27.77:30303",
"enode://4f7a27820107c235bb0f8086ee1c2bad62174450ec2eec12cb29e3fa7ecb9f332710373c1d11a3115aa72f2dabbae27b73eac51f06d3df558dd9fb51007da653@52.91.112.249:30303",
"enode://6b58a9437aa88f254b75110019c54807cf1d7da9729f2c022a2463bae86b639288909fe00ffac0599e616676eea2de3c503bacaf4be835a02195bea0b349ca80@54.88.246.77:30303",
"enode://562051180eca42514e44b4428ed20a3cb626654631f53bbfa549de7d3b7e418376e8f784c232429d7ff01bd0597e3ce7327699bb574d39ac3b2ac1729ed0dd44@54.224.110.32:30303"
]

View File

@ -14,9 +14,13 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import time
import geth
import maya
from geth.chain import write_genesis_file, initialize_chain
from twisted.logger import Logger
from web3.exceptions import BlockNotFound
from web3.middleware import geth_poa_middleware
from constant_sorrow.constants import NO_BLOCKCHAIN_AVAILABLE
@ -39,11 +43,15 @@ class Blockchain:
class ConnectionNotEstablished(RuntimeError):
pass
class SyncTimeout(RuntimeError):
pass
def __init__(self,
provider_process=None,
interface: Union[BlockchainInterface, BlockchainDeployerInterface] = None):
self.log = Logger("blockchain")
self.__provider_process = provider_process
# Default interface
@ -66,6 +74,66 @@ class Blockchain:
def interface(self) -> Union[BlockchainInterface, BlockchainDeployerInterface]:
return self.__interface
@property
def peers(self):
if self._instance is NO_BLOCKCHAIN_AVAILABLE:
raise self.ConnectionNotEstablished
return self.interface.w3.geth.admin.peers()
@property
def syncing(self):
if self._instance is NO_BLOCKCHAIN_AVAILABLE:
raise self.ConnectionNotEstablished
return self.interface.w3.eth.syncing
def sync(self, timeout: int = 600):
"""
Blocking call that waits for at least one ethereum peer and a full blockchain sync.
"""
# Record start time for timeout calculation
now = maya.now()
start_time = now
def check_for_timeout(timeout=timeout):
last_update = maya.now()
duration = (last_update - start_time).seconds
if duration > timeout:
raise self.SyncTimeout
# Check for ethereum peers
self.log.info(f"Waiting for ethereum peers...")
while not self.peers:
time.sleep(0)
check_for_timeout(timeout=5)
needs_sync = False
for peer in self.peers:
peer_block_header = peer['protocols']['eth']['head']
try:
self.interface.w3.eth.getBlock(peer_block_header)
except BlockNotFound:
needs_sync = True
break
# Start
if needs_sync:
peers = len(self.peers)
self.log.info(f"Waiting for sync to begin ({peers} ethereum peers)")
while not self.syncing:
time.sleep(0)
check_for_timeout()
# Continue until done
while self.syncing:
current = self.syncing['currentBlock']
total = self.syncing['highestBlock']
self.log.info(f"Syncing {current}/{total}")
time.sleep(1)
check_for_timeout()
return True
@classmethod
def connect(cls,
provider_process=None,
@ -75,7 +143,8 @@ class Blockchain:
compile: bool = False,
poa: bool = False,
force: bool = True,
fetch_registry: bool = True
fetch_registry: bool = True,
full_sync: bool = True,
) -> 'Blockchain':
log = Logger('blockchain-init')
@ -106,6 +175,7 @@ class Blockchain:
interface.w3.middleware_onion.inject(geth_poa_middleware, layer=0)
cls._instance = cls(interface=interface, provider_process=provider_process)
else:
if provider_uri is not None:
@ -119,6 +189,8 @@ class Blockchain:
# but we want to connect using a different registry.
cls._instance.interface.registry = registry
# Syn blockchain
cls._instance.sync()
return cls._instance
@classmethod

View File

@ -1,5 +1,7 @@
import json
import os
import shutil
import time
from constant_sorrow.constants import NOT_RUNNING
from eth_utils import to_checksum_address, is_checksum_address
@ -14,23 +16,33 @@ from geth.chain import (
from geth.process import BaseGethProcess
from twisted.logger import Logger
from nucypher.config.constants import DEFAULT_CONFIG_ROOT, BASE_DIR, DEPLOY_DIR
from nucypher.config.constants import DEFAULT_CONFIG_ROOT, BASE_DIR, DEPLOY_DIR, USER_LOG_DIR
NUCYPHER_CHAIN_IDS = {
'devnet': 112358,
}
class NuCypherGethProcess(BaseGethProcess, LoggingMixin):
class NuCypherGethProcess(LoggingMixin, BaseGethProcess):
IPC_PROTOCOL = 'http'
IPC_FILENAME = 'geth.ipc'
VERBOSITY = 5
LOG_PATH = os.path.join(USER_LOG_DIR, 'nucypher-geth.log')
_CHAIN_NAME = NotImplemented
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self,
geth_kwargs: dict,
stdout_logfile_path: str = LOG_PATH,
stderr_logfile_path: str = LOG_PATH,
*args, **kwargs):
super().__init__(geth_kwargs=geth_kwargs,
stdout_logfile_path=stdout_logfile_path,
stderr_logfile_path=stderr_logfile_path,
*args, **kwargs)
self.log = Logger('nucypher-geth')
@property
@ -47,19 +59,20 @@ class NuCypherGethProcess(BaseGethProcess, LoggingMixin):
uri = f"{scheme}://{location}"
return uri
def start(self, timeout: int = 30):
def start(self, timeout: int = 30, extra_delay: int = 1):
self.log.info("STARTING GETH NOW")
super().start()
self.wait_for_ipc(timeout=timeout) # on for all nodes by default
if self.IPC_PROTOCOL in ('rpc', 'http'):
self.wait_for_rpc(timeout=timeout)
time.sleep(extra_delay)
class NuCypherGethDevProcess(NuCypherGethProcess):
_CHAIN_NAME = 'poa-development'
def __init__(self, config_root: str = None):
def __init__(self, config_root: str = None, *args, **kwargs):
base_dir = config_root if config_root else DEFAULT_CONFIG_ROOT
base_dir = os.path.join(base_dir, '.ethereum')
@ -67,7 +80,7 @@ class NuCypherGethDevProcess(NuCypherGethProcess):
ipc_path = os.path.join(self.data_dir, 'geth.ipc')
self.geth_kwargs = {'ipc_path': ipc_path}
super().__init__(geth_kwargs=self.geth_kwargs)
super().__init__(geth_kwargs=self.geth_kwargs, *args, **kwargs)
self.geth_kwargs.update({'dev': True})
self.command = [*self.command, '--dev']
@ -85,7 +98,8 @@ class NuCypherGethDevnetProcess(NuCypherGethProcess):
def __init__(self,
config_root: str = None,
overrides: dict = None):
overrides: dict = None,
*args, **kwargs):
log = Logger('nucypher-geth-devnet')
@ -113,6 +127,8 @@ class NuCypherGethDevnetProcess(NuCypherGethProcess):
'verbosity': str(self.VERBOSITY),
'data_dir': self.data_dir,
'ipc_path': ipc_path,
'rpc_enabled': True,
'no_discover': True,
}
# Genesis & Blockchain Init
@ -130,7 +146,7 @@ class NuCypherGethDevnetProcess(NuCypherGethProcess):
self.initialized = True
self.__process = NOT_RUNNING
super().__init__(geth_kwargs) # Attaches self.geth_kwargs in super call
super().__init__(geth_kwargs=geth_kwargs, *args, **kwargs) # Attaches self.geth_kwargs in super call
self.command = [*self.command, '--syncmode', 'fast']
def get_accounts(self):
@ -147,6 +163,10 @@ class NuCypherGethDevnetProcess(NuCypherGethProcess):
log.info(f'Initializing new blockchain database and genesis block.')
initialize_chain(genesis_data=genesis_data, **self.geth_kwargs)
# Write static nodes file to data dir
bootnodes_filepath = os.path.join(DEPLOY_DIR, 'static-nodes.json')
shutil.copy(bootnodes_filepath, os.path.join(self.data_dir))
def ensure_account_exists(self, password: str) -> str:
accounts = get_accounts(**self.geth_kwargs)
if not accounts:
@ -157,3 +177,6 @@ class NuCypherGethDevnetProcess(NuCypherGethProcess):
checksum_address = to_checksum_address(account.decode())
assert is_checksum_address(checksum_address), f"GETH RETURNED INVALID ETH ADDRESS {checksum_address}"
return checksum_address
def start(self, *args, **kwargs):
super().start()

View File

@ -1,6 +1,7 @@
import os
import click
from constant_sorrow.constants import NO_BLOCKCHAIN_CONNECTION
from nucypher.blockchain.eth.clients import NuCypherGethDevnetProcess
from nucypher.characters.banners import FELIX_BANNER
@ -9,7 +10,6 @@ from nucypher.cli.config import nucypher_click_config
from nucypher.cli.types import NETWORK_PORT, EXISTING_READABLE_FILE, EIP55_CHECKSUM_ADDRESS
from nucypher.config.characters import FelixConfiguration
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
from constant_sorrow.constants import NO_BLOCKCHAIN_CONNECTION
@click.command()
@ -197,7 +197,7 @@ ETH ........ {str(eth_balance)}
elif action == 'run': # Start web services
try:
FELIX.blockchain.connect()
FELIX.blockchain.sync()
FELIX.start(host=host,
port=port,
web_services=not dry_run,

View File

@ -302,7 +302,10 @@ class NodeConfiguration(ABC):
def known_nodes(self):
return self.__fleet_state
def connect_to_blockchain(self, enode: str = None, recompile_contracts: bool = False) -> None:
def connect_to_blockchain(self,
enode: str = None,
recompile_contracts: bool = False,
full_sync: bool = False) -> None:
"""
:param enode: ETH seednode or bootnode enode address to start learning from,
@ -318,7 +321,8 @@ class NodeConfiguration(ABC):
compile=recompile_contracts,
poa=self.poa,
fetch_registry=True,
provider_process=self.provider_process)
provider_process=self.provider_process,
full_sync=full_sync)
# Read Ethereum Node Keyring
self.accounts = self.blockchain.interface.w3.eth.accounts