Skip to content

Commit 124a00e

Browse files
committed
refactor(sync-v2): introduce all structures needed without any sync-v2
1 parent 490f37c commit 124a00e

File tree

92 files changed

+2428
-560
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+2428
-560
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
test:
1414
name: python-${{ matrix.python }} (${{ matrix.os }})
1515
runs-on: ${{ matrix.os }}
16-
timeout-minutes: 40 # default is 360
16+
timeout-minutes: 60 # default is 360
1717
strategy:
1818
fail-fast: false
1919
matrix:

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ tests-doctests:
3636
tests-lib:
3737
pytest --durations=10 $(pytest_flags) --doctest-modules hathor $(tests_lib)
3838

39+
.PHONY: tests-quick
40+
tests-quick:
41+
pytest --durations=10 $(pytest_flags) --doctest-modules hathor $(tests_lib) --maxfail=1
42+
3943
.PHONY: tests-genesis
4044
tests-genesis:
4145
HATHOR_TEST_CONFIG_FILE=hathor.conf.mainnet pytest tests/tx/test_genesis.py

hathor/cli/run_node.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ def create_parser(self) -> ArgumentParser:
8080
parser.add_argument('--max-output-script-size', type=int, default=None, help='Custom max accepted script size '
8181
'on /push-tx API')
8282
parser.add_argument('--sentry-dsn', help='Sentry DSN')
83+
parser.add_argument('--x-sync-bridge', action='store_true',
84+
help='Enable support for running both sync protocols. DO NOT ENABLE, IT WILL BREAK.')
8385
return parser
8486

8587
def prepare(self, args: Namespace) -> None:
@@ -227,7 +229,8 @@ def create_wallet():
227229
network = settings.NETWORK_NAME
228230
self.manager = HathorManager(reactor, peer_id=peer_id, network=network, hostname=hostname,
229231
tx_storage=self.tx_storage, wallet=self.wallet, wallet_index=args.wallet_index,
230-
stratum_port=args.stratum, ssl=True, checkpoints=settings.CHECKPOINTS)
232+
stratum_port=args.stratum, ssl=True, checkpoints=settings.CHECKPOINTS,
233+
enable_sync_v1=True, enable_sync_v2=args.x_sync_bridge)
231234
if args.allow_mining_without_peers:
232235
self.manager.allow_mining_without_peers()
233236

hathor/consensus.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,14 @@ def update_voided_info(self, block: Block) -> None:
154154

155155
# Update accumulated weight of the transactions voiding us.
156156
assert block.hash not in voided_by
157-
for h in voided_by:
157+
for h in sorted(voided_by):
158158
tx = storage.get_transaction(h)
159159
tx_meta = tx.get_metadata()
160160
tx_meta.accumulated_weight = sum_weights(tx_meta.accumulated_weight, block.weight)
161161
storage.save_transaction(tx, only_metadata=True)
162162

163163
# Check conflicts of the transactions voiding us.
164-
for h in voided_by:
164+
for h in sorted(voided_by):
165165
tx = storage.get_transaction(h)
166166
if not tx.is_block:
167167
assert isinstance(tx, Transaction)

hathor/indexes.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,29 @@ class IndexesManager:
4444
so it will know which index is better to use in each moment
4545
"""
4646

47-
def __init__(self) -> None:
48-
self.tips_index = TipsIndex()
47+
def __init__(self, with_tips_index: bool = True) -> None:
48+
self.tips_index = TipsIndex() if with_tips_index else None
4949
self.txs_index = TransactionsIndex()
5050

5151
def add_tx(self, tx: BaseTransaction) -> bool:
5252
""" Add a transaction to the indexes
5353
5454
:param tx: Transaction to be added
5555
"""
56-
r1 = self.tips_index.add_tx(tx)
57-
r2 = self.txs_index.add_tx(tx)
58-
assert r1 == r2
56+
r1 = self.txs_index.add_tx(tx)
57+
if self.tips_index is not None:
58+
r2 = self.tips_index.add_tx(tx)
59+
assert r1 == r2
5960
return r1
6061

6162
def del_tx(self, tx: BaseTransaction, *, relax_assert: bool = False) -> None:
6263
""" Delete a transaction from the indexes
6364
6465
:param tx: Transaction to be deleted
6566
"""
66-
self.tips_index.del_tx(tx, relax_assert=relax_assert)
6767
self.txs_index.del_tx(tx)
68+
if self.tips_index is not None:
69+
self.tips_index.del_tx(tx, relax_assert=relax_assert)
6870

6971
def get_newest(self, count: int) -> Tuple[List[bytes], bool]:
7072
""" Get transactions or blocks in txs_index from the newest to the oldest

hathor/manager.py

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
from hathor.transaction.exceptions import TxValidationError
4242
from hathor.transaction.storage import TransactionStorage
4343
from hathor.transaction.storage.exceptions import TransactionDoesNotExist
44-
from hathor.util import LogDuration, random_choices
44+
from hathor.util import LogDuration, not_none, random_choices
4545
from hathor.wallet import BaseWallet
4646

4747
settings = HathorSettings()
@@ -67,6 +67,7 @@ def __init__(self, reactor: IReactorCore, peer_id: Optional[PeerId] = None, netw
6767
wallet: Optional[BaseWallet] = None, tx_storage: Optional[TransactionStorage] = None,
6868
peer_storage: Optional[Any] = None, default_port: int = 40403, wallet_index: bool = False,
6969
stratum_port: Optional[int] = None, ssl: bool = True,
70+
enable_sync_v1: bool = True, enable_sync_v2: bool = False,
7071
capabilities: Optional[List[str]] = None, checkpoints: Optional[List[Checkpoint]] = None,
7172
rng: Optional[Rng] = None) -> None:
7273
"""
@@ -81,7 +82,7 @@ def __init__(self, reactor: IReactorCore, peer_id: Optional[PeerId] = None, netw
8182
:param pubsub: If not given, a new one is created.
8283
:type pubsub: :py:class:`hathor.pubsub.PubSubManager`
8384
84-
:param tx_storage: If not given, a :py:class:`TransactionMemoryStorage` one is created.
85+
:param tx_storage: Required storage backend.
8586
:type tx_storage: :py:class:`hathor.transaction.storage.transaction_storage.TransactionStorage`
8687
8788
:param peer_storage: If not given, a new one is created.
@@ -99,7 +100,12 @@ def __init__(self, reactor: IReactorCore, peer_id: Optional[PeerId] = None, netw
99100
from hathor.metrics import Metrics
100101
from hathor.p2p.factory import HathorClientFactory, HathorServerFactory
101102
from hathor.p2p.manager import ConnectionsManager
102-
from hathor.transaction.storage.memory_storage import TransactionMemoryStorage
103+
104+
if not (enable_sync_v1 or enable_sync_v2):
105+
raise TypeError(f'{type(self).__name__}() at least one sync version is required')
106+
107+
if tx_storage is None:
108+
raise TypeError(f'{type(self).__name__}() missing 1 required positional argument: \'tx_storage\'')
103109

104110
self.log = logger.new()
105111

@@ -138,7 +144,7 @@ def __init__(self, reactor: IReactorCore, peer_id: Optional[PeerId] = None, netw
138144

139145
# XXX Should we use a singleton or a new PeerStorage? [msbrogli 2018-08-29]
140146
self.pubsub = pubsub or PubSubManager(self.reactor)
141-
self.tx_storage = tx_storage or TransactionMemoryStorage()
147+
self.tx_storage = tx_storage
142148
self.tx_storage.pubsub = self.pubsub
143149
if wallet_index and self.tx_storage.with_index:
144150
self.tx_storage.wallet_index = WalletIndex(self.pubsub)
@@ -156,10 +162,12 @@ def __init__(self, reactor: IReactorCore, peer_id: Optional[PeerId] = None, netw
156162
self.peer_discoveries: List[PeerDiscovery] = []
157163

158164
self.ssl = ssl
159-
self.server_factory = HathorServerFactory(self.network, self.my_peer, node=self, use_ssl=ssl)
160-
self.client_factory = HathorClientFactory(self.network, self.my_peer, node=self, use_ssl=ssl)
165+
self.server_factory = HathorServerFactory(self.network, self.my_peer, node=self, use_ssl=ssl,
166+
enable_sync_v1=enable_sync_v1, enable_sync_v2=enable_sync_v2)
167+
self.client_factory = HathorClientFactory(self.network, self.my_peer, node=self, use_ssl=ssl,
168+
enable_sync_v1=enable_sync_v1, enable_sync_v2=enable_sync_v2)
161169
self.connections = ConnectionsManager(self.reactor, self.my_peer, self.server_factory, self.client_factory,
162-
self.pubsub, self, ssl, rng=self.rng)
170+
self.pubsub, self, ssl, whitelist_only=False, rng=self.rng)
163171

164172
self.wallet = wallet
165173
if self.wallet:
@@ -193,6 +201,8 @@ def __init__(self, reactor: IReactorCore, peer_id: Optional[PeerId] = None, netw
193201
# List of capabilities of the peer
194202
if capabilities is not None:
195203
self.capabilities = capabilities
204+
elif enable_sync_v2:
205+
self.capabilities = [settings.CAPABILITY_WHITELIST, settings.CAPABILITY_SYNC_V2]
196206
else:
197207
self.capabilities = [settings.CAPABILITY_WHITELIST]
198208

@@ -507,19 +517,33 @@ def generate_parent_txs(self, timestamp: Optional[float]) -> 'ParentTxs':
507517
This method tries to return a stable result, such that for a given timestamp and storage state it will always
508518
return the same.
509519
"""
510-
if timestamp is None:
511-
timestamp = self.reactor.seconds()
512-
can_include_intervals = sorted(self.tx_storage.get_tx_tips(timestamp - 1))
513-
assert can_include_intervals, 'tips cannot be empty'
514-
max_timestamp = max(int(i.begin) for i in can_include_intervals)
515-
must_include: List[bytes] = []
516-
assert len(can_include_intervals) > 0, f'invalid timestamp "{timestamp}", no tips found"'
517-
if len(can_include_intervals) < 2:
518-
# If there is only one tip, let's randomly choose one of its parents.
519-
must_include_interval = can_include_intervals[0]
520-
must_include = [must_include_interval.data]
521-
can_include_intervals = sorted(self.tx_storage.get_tx_tips(must_include_interval.begin - 1))
522-
can_include = [i.data for i in can_include_intervals]
520+
# get all that are before "timestamp"
521+
# XXX: maybe add some tolerance?
522+
tx_tips = list(self.tx_storage.iter_tx_tips(timestamp))
523+
must_include: List[bytes]
524+
can_include: List[bytes]
525+
max_timestamp: int
526+
if not tx_tips:
527+
# there are no txs on mempool, repeat the same tx parents as the best block
528+
must_include = []
529+
# can_include = self.tx_storage.get_best_block().parents[1:]
530+
best_block = self.tx_storage.get_best_block()
531+
if best_block.is_genesis:
532+
can_include = [settings.GENESIS_TX1_HASH, settings.GENESIS_TX2_HASH]
533+
else:
534+
can_include = self.tx_storage.get_best_block().parents[1:]
535+
max_timestamp = max(tx.timestamp for tx in map(self.tx_storage.get_transaction, can_include) if tx)
536+
elif len(tx_tips) < 2:
537+
# there is only one tx, it must be included, and either of its parents can be included
538+
only_tx = tx_tips[0]
539+
must_include = [not_none(tx.hash) for tx in tx_tips]
540+
can_include = only_tx.parents[:]
541+
max_timestamp = only_tx.timestamp
542+
else:
543+
# otherwise we can include any tx on the mempool
544+
must_include = []
545+
can_include = [not_none(tx.hash) for tx in tx_tips]
546+
max_timestamp = max(tx.timestamp for tx in tx_tips)
523547
return ParentTxs(max_timestamp, can_include, must_include)
524548

525549
def allow_mining_without_peers(self) -> None:
@@ -646,6 +670,17 @@ def _make_block_template(self, parent_block: Block, parent_txs: 'ParentTxs', cur
646670
score=sum_weights(parent_block_metadata.score, weight),
647671
)
648672

673+
def get_current_score(self, parent_block: Optional[Block] = None, timestamp: Optional[int] = None) -> float:
674+
if timestamp is None:
675+
timestamp = int(max(self.tx_storage.latest_timestamp, self.reactor.seconds()))
676+
if parent_block is None:
677+
block = self.tx_storage.get_transaction(next(iter(self.tx_storage.get_best_block_tips())))
678+
assert isinstance(block, Block)
679+
parent_block = block
680+
parent_block_metadata = parent_block.get_metadata()
681+
weight = daa.calculate_next_weight(parent_block, timestamp)
682+
return sum_weights(parent_block_metadata.score, weight)
683+
649684
def generate_mining_block(self, timestamp: Optional[int] = None,
650685
parent_block_hash: Optional[bytes] = None,
651686
data: bytes = b'', address: Optional[bytes] = None,

hathor/p2p/downloader.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
settings = HathorSettings()
2727

2828
if TYPE_CHECKING:
29-
from hathor.manager import HathorManager # noqa: F401
30-
from hathor.p2p.node_sync import NodeSyncTimestamp # noqa: F401
31-
from hathor.transaction import BaseTransaction # noqa: F401
29+
from hathor.manager import HathorManager
30+
from hathor.p2p.node_sync import NodeSyncTimestamp
31+
from hathor.transaction import BaseTransaction
3232

3333
logger = get_logger()
3434

hathor/p2p/factory.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,20 @@ def __init__(
4242
*,
4343
node: 'HathorManager',
4444
use_ssl: bool,
45+
enable_sync_v1: bool,
46+
enable_sync_v2: bool,
4547
):
48+
if not (enable_sync_v1 or enable_sync_v2):
49+
raise ValueError('At least one sync version is required')
50+
4651
super().__init__()
4752
self.network = network
4853
self.my_peer = my_peer
4954
self.connections = connections
5055
self.node = node
5156
self.use_ssl = use_ssl
57+
self.enable_sync_v1 = enable_sync_v1
58+
self.enable_sync_v2 = enable_sync_v2
5259

5360
def buildProtocol(self, addr: Tuple[str, int]) -> MyServerProtocol:
5461
p = self.protocol(
@@ -58,6 +65,8 @@ def buildProtocol(self, addr: Tuple[str, int]) -> MyServerProtocol:
5865
node=self.node,
5966
use_ssl=self.use_ssl,
6067
inbound=True,
68+
enable_sync_v1=self.enable_sync_v1,
69+
enable_sync_v2=self.enable_sync_v2,
6170
)
6271
p.factory = self
6372
return p
@@ -78,13 +87,20 @@ def __init__(
7887
*,
7988
node: 'HathorManager',
8089
use_ssl: bool,
90+
enable_sync_v1: bool,
91+
enable_sync_v2: bool,
8192
):
93+
if not (enable_sync_v1 or enable_sync_v2):
94+
raise ValueError('At least one sync version is required')
95+
8296
super().__init__()
8397
self.network = network
8498
self.my_peer = my_peer
8599
self.connections = connections
86100
self.node = node
87101
self.use_ssl = use_ssl
102+
self.enable_sync_v1 = enable_sync_v1
103+
self.enable_sync_v2 = enable_sync_v2
88104

89105
def buildProtocol(self, addr: Tuple[str, int]) -> MyClientProtocol:
90106
p = self.protocol(
@@ -94,6 +110,8 @@ def buildProtocol(self, addr: Tuple[str, int]) -> MyClientProtocol:
94110
node=self.node,
95111
use_ssl=self.use_ssl,
96112
inbound=False,
113+
enable_sync_v1=self.enable_sync_v1,
114+
enable_sync_v2=self.enable_sync_v2,
97115
)
98116
p.factory = self
99117
return p

hathor/p2p/manager.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,11 @@ class ConnectionsManager:
5252
connected_peers: Dict[str, HathorProtocol]
5353
connecting_peers: Dict[IStreamClientEndpoint, Deferred]
5454
handshaking_peers: Set[HathorProtocol]
55+
whitelist_only: bool
5556

5657
def __init__(self, reactor: ReactorBase, my_peer: PeerId, server_factory: 'HathorServerFactory',
5758
client_factory: 'HathorClientFactory', pubsub: PubSubManager, manager: 'HathorManager',
58-
ssl: bool, rng: Rng) -> None:
59+
ssl: bool, whitelist_only: bool, rng: Rng) -> None:
5960
self.log = logger.new()
6061
self.rng = rng
6162

@@ -106,6 +107,9 @@ def __init__(self, reactor: ReactorBase, my_peer: PeerId, server_factory: 'Hatho
106107

107108
self.ssl = ssl
108109

110+
# Parameter to explicitly enable whitelist-only mode, when False it will still check the whitelist for sync-v1
111+
self.whitelist_only = whitelist_only
112+
109113
def start(self) -> None:
110114
self.lc_reconnect.start(5, now=False)
111115
if settings.ENABLE_PEER_WHITELIST:

hathor/p2p/messages.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,25 @@ class ProtocolMessages(Enum):
9090
GET_NEXT = 'GET-NEXT'
9191
NEXT = 'NEXT'
9292

93-
GET_BLOCKS = 'GET-BLOCKS' # Request a list of hashes for blocks. Payload is the current latest block.
94-
BLOCKS = 'BLOCKS' # Send a list of hashes for blocks. Payload is a list of hashes.
93+
# Sync-v2 messages
9594

96-
GET_TRANSACTIONS = 'GET-TRANSACTIONS' # Request a list of hashes for transactions.
97-
TRANSACTIONS = 'TRANSACTIONS' # Send a list of hashes for transactions.
95+
GET_NEXT_BLOCKS = 'GET-NEXT-BLOCKS'
96+
BLOCKS = 'BLOCKS'
97+
BLOCKS_END = 'BLOCKS-END'
9898

99-
HASHES = 'HASHES'
99+
GET_BEST_BLOCK = 'GET-BEST-BLOCK' # Request the best block of the peer
100+
BEST_BLOCK = 'BEST-BLOCK' # Send the best block to your peer
101+
102+
GET_BLOCK_TXS = 'GET-BLOCK-TXS' # TODO: rename, maybe GET-TX-RANGE or repurpose GET-TRANSACTIONS above
103+
TRANSACTION = 'TRANSACTION'
104+
105+
GET_MEMPOOL = 'GET-MEMPOOL' # TODO: rename, maybe GET-TX-RANGE or repurpose GET-TRANSACTIONS above
106+
MEMPOOL_END = 'MEMPOOL-END' # End of mempool sync
107+
108+
GET_COMMON_CHAIN = 'GET-COMMON-CHAIN'
109+
COMMON_CHAIN = 'COMMON-CHAIN'
110+
111+
GET_PEER_BLOCK_HASHES = 'GET-PEER-BLOCK-HASHES'
112+
PEER_BLOCK_HASHES = 'PEER-BLOCK-HASHES'
113+
114+
STOP_BLOCK_STREAMING = 'STOP-BLOCK-STREAMING'

0 commit comments

Comments
 (0)