Skip to content

Commit a4e34f1

Browse files
committed
improve migration
1 parent 497fc4c commit a4e34f1

File tree

6 files changed

+81
-65
lines changed

6 files changed

+81
-65
lines changed

hathor/transaction/storage/cache_storage.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from collections import OrderedDict
1616
from typing import Any, Iterator, Optional
1717

18+
from structlog.stdlib import BoundLogger
1819
from twisted.internet import threads
1920
from typing_extensions import override
2021

@@ -25,7 +26,6 @@
2526
from hathor.transaction.storage.migrations import MigrationState
2627
from hathor.transaction.storage.transaction_storage import BaseTransactionStorage
2728
from hathor.transaction.storage.tx_allow_scope import TxAllowScope
28-
from hathor.types import VertexId
2929

3030

3131
class TransactionCacheStorage(BaseTransactionStorage):
@@ -253,5 +253,5 @@ def flush(self):
253253
self._flush_to_storage(self.dirty_txs.copy())
254254

255255
@override
256-
def iter_all_raw_metadata(self) -> Iterator[tuple[VertexId, dict[str, Any]]]:
257-
return self.store.iter_all_raw_metadata()
256+
def migrate_static_metadata(self, log: BoundLogger) -> None:
257+
return self.store.migrate_static_metadata(log)

hathor/transaction/storage/memory_storage.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from typing import Any, Iterator, Optional, TypeVar
1616

17+
from structlog.stdlib import BoundLogger
1718
from typing_extensions import override
1819

1920
from hathor.conf.settings import HathorSettings
@@ -23,7 +24,6 @@
2324
from hathor.transaction.storage.migrations import MigrationState
2425
from hathor.transaction.storage.transaction_storage import BaseTransactionStorage
2526
from hathor.transaction.transaction_metadata import TransactionMetadata
26-
from hathor.types import VertexId
2727

2828
_Clonable = TypeVar('_Clonable', BaseTransaction, TransactionMetadata)
2929

@@ -128,7 +128,7 @@ def get_value(self, key: str) -> Optional[str]:
128128
return self.attributes.get(key)
129129

130130
@override
131-
def iter_all_raw_metadata(self) -> Iterator[tuple[VertexId, dict[str, Any]]]:
132-
# This method is only ever used by the `migrate_static_metadata` migration, and therefore it is not necessary
133-
# to implement for the memory storage.
131+
def migrate_static_metadata(self, log: BoundLogger) -> None:
132+
# This method is only ever used by the `migrate_static_metadata` migration, and therefore must not be
133+
# implemented for the memory storage.
134134
raise NotImplementedError

hathor/transaction/storage/migrations/migrate_static_metadata.py

+22-42
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@
1313
# limitations under the License.
1414

1515
from typing import TYPE_CHECKING
16-
from unittest.mock import Mock
1716

1817
from structlog import get_logger
1918

2019
from hathor.conf.get_settings import get_global_settings
21-
from hathor.transaction import BaseTransaction
22-
from hathor.transaction.static_metadata import BlockStaticMetadata, TransactionStaticMetadata, VertexStaticMetadata
20+
from hathor.transaction import Block, Transaction
21+
from hathor.transaction.static_metadata import BlockStaticMetadata, TransactionStaticMetadata
2322
from hathor.transaction.storage.migrations import BaseMigration
2423
from hathor.util import progress
2524

@@ -40,48 +39,29 @@ def run(self, storage: 'TransactionStorage') -> None:
4039
"""This migration takes attributes from existing vertex metadata and saves them as static metadata."""
4140
log = logger.new()
4241
settings = get_global_settings()
43-
# We have to iterate over metadata instead of vertices because the storage doesn't allow us to get a vertex if
44-
# its static metadata is not set. We also use raw dict metadata because `metadata.create_from_json()` doesn't
45-
# include attributes that should be static, which are exactly the ones we need for this migration.
46-
metadata_iter = storage.iter_all_raw_metadata()
4742

48-
for vertex_id, raw_metadata in progress(metadata_iter, log=log, total=None):
49-
height = raw_metadata['height']
50-
min_height = raw_metadata['min_height']
51-
bit_counts = raw_metadata.get('feature_activation_bit_counts')
43+
# First we migrate static metadata using the storage itself since it uses internal structures.
44+
log.info('creating static metadata...')
45+
storage.migrate_static_metadata(log)
5246

53-
assert isinstance(height, int)
54-
assert isinstance(min_height, int)
47+
# Now that static metadata is set, we can use the topological iterator normally
48+
log.info('removing old metadata and validating...')
49+
topological_iter = storage.topological_iterator()
5550

56-
static_metadata: VertexStaticMetadata
57-
is_block = (vertex_id == settings.GENESIS_BLOCK_HASH or height != 0)
58-
59-
if is_block:
60-
assert isinstance(bit_counts, list)
61-
for item in bit_counts:
62-
assert isinstance(item, int)
51+
for vertex in progress(topological_iter, log=log, total=None):
52+
# We re-save the vertex's metadata so it's serialized with the new `to_bytes()` method, excluding fields
53+
# that were migrated.
54+
storage.save_transaction(vertex, only_metadata=True)
6355

64-
static_metadata = BlockStaticMetadata(
65-
height=height,
66-
min_height=min_height,
67-
feature_activation_bit_counts=bit_counts,
68-
feature_states={}, # This will be populated in the next PR
56+
# We re-create the static metadata from scratch and compare it with the value that was created by the
57+
# migration above, as a sanity check.
58+
if isinstance(vertex, Block):
59+
assert vertex.static_metadata == BlockStaticMetadata.create_from_storage(
60+
vertex, settings, storage
6961
)
70-
else:
71-
assert bit_counts is None or bit_counts == []
72-
static_metadata = TransactionStaticMetadata(
73-
min_height=min_height
62+
elif isinstance(vertex, Transaction):
63+
assert vertex.static_metadata == TransactionStaticMetadata.create_from_storage(
64+
vertex, settings, storage
7465
)
75-
76-
# We create a fake vertex with just the hash and static metadata, so we can use the existing
77-
# `storage._save_static_metadata()` instead of having to create an unsafe storage API that takes those
78-
# two arguments.
79-
mock_vertex = Mock(spec_set=BaseTransaction)
80-
mock_vertex.hash = vertex_id
81-
mock_vertex.static_metadata = static_metadata
82-
storage._save_static_metadata(mock_vertex)
83-
84-
# Now we can take the actual vertex from the storage and save its metadata. It'll be serialized with the
85-
# new `to_bytes()` method, excluding fields that were migrated.
86-
vertex = storage.get_transaction(vertex_id)
87-
storage.save_transaction(vertex, only_metadata=True)
66+
else:
67+
raise NotImplementedError

hathor/transaction/storage/rocksdb_storage.py

+41-9
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,21 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from typing import TYPE_CHECKING, Any, Iterator, Optional
15+
from typing import TYPE_CHECKING, Iterator, Optional
1616

1717
from structlog import get_logger
18+
from structlog.stdlib import BoundLogger
1819
from typing_extensions import override
1920

2021
from hathor.conf.settings import HathorSettings
2122
from hathor.indexes import IndexesManager
2223
from hathor.storage import RocksDBStorage
23-
from hathor.transaction.static_metadata import VertexStaticMetadata
24+
from hathor.transaction.static_metadata import BlockStaticMetadata, TransactionStaticMetadata, VertexStaticMetadata
2425
from hathor.transaction.storage.exceptions import TransactionDoesNotExist
2526
from hathor.transaction.storage.migrations import MigrationState
2627
from hathor.transaction.storage.transaction_storage import BaseTransactionStorage
2728
from hathor.transaction.vertex_parser import VertexParser
28-
from hathor.types import VertexId
29-
from hathor.util import json_loadb
29+
from hathor.util import json_loadb, progress
3030

3131
if TYPE_CHECKING:
3232
import rocksdb
@@ -235,9 +235,41 @@ def get_value(self, key: str) -> Optional[str]:
235235
return data.decode()
236236

237237
@override
238-
def iter_all_raw_metadata(self) -> Iterator[tuple[VertexId, dict[str, Any]]]:
239-
items = self._db.iteritems(self._cf_meta)
240-
items.seek_to_first()
238+
def migrate_static_metadata(self, log: BoundLogger) -> None:
239+
metadata_iter = self._db.iteritems(self._cf_meta)
240+
metadata_iter.seek_to_first()
241+
242+
# We have to iterate over metadata instead of vertices because the storage doesn't allow us to get a vertex if
243+
# its static metadata is not set. We also use raw dict metadata because `metadata.create_from_json()` doesn't
244+
# include attributes that should be static, which are exactly the ones we need for this migration.
245+
for (_, vertex_id), metadata_bytes in progress(metadata_iter, log=log, total=None):
246+
raw_metadata = json_loadb(metadata_bytes)
247+
height = raw_metadata['height']
248+
min_height = raw_metadata['min_height']
249+
bit_counts = raw_metadata.get('feature_activation_bit_counts')
250+
251+
assert isinstance(height, int)
252+
assert isinstance(min_height, int)
253+
254+
static_metadata: VertexStaticMetadata
255+
is_block = (vertex_id == self._settings.GENESIS_BLOCK_HASH or height != 0)
256+
257+
if is_block:
258+
assert isinstance(bit_counts, list)
259+
for item in bit_counts:
260+
assert isinstance(item, int)
261+
262+
static_metadata = BlockStaticMetadata(
263+
height=height,
264+
min_height=min_height,
265+
feature_activation_bit_counts=bit_counts,
266+
feature_states={}, # This will be populated in the next PR
267+
)
268+
else:
269+
assert bit_counts is None or bit_counts == []
270+
static_metadata = TransactionStaticMetadata(
271+
min_height=min_height
272+
)
241273

242-
for (_, vertex_id), metadata_bytes in items:
243-
yield vertex_id, json_loadb(metadata_bytes)
274+
# Save it manually to the CF
275+
self._db.put((self._cf_static_meta, vertex_id), static_metadata.to_bytes())

hathor/transaction/storage/transaction_storage.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from intervaltree.interval import Interval
2424
from structlog import get_logger
25+
from structlog.stdlib import BoundLogger
2526

2627
from hathor.conf.settings import HathorSettings
2728
from hathor.execution_manager import ExecutionManager
@@ -1126,10 +1127,9 @@ def get_block(self, block_id: VertexId) -> Block:
11261127
return block
11271128

11281129
@abstractmethod
1129-
def iter_all_raw_metadata(self) -> Iterator[tuple[VertexId, dict[str, Any]]]:
1130+
def migrate_static_metadata(self, log: BoundLogger) -> None:
11301131
"""
1131-
Iterate over all vertex metadata from this storage, as raw dicts. This is only used for the
1132-
`migrate_static_metadata` migration.
1132+
Migrate metadata attributes to static metadata. This is only used for the `migrate_static_metadata` migration.
11331133
"""
11341134
raise NotImplementedError
11351135

hathor/transaction/transaction_metadata.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -298,11 +298,15 @@ def to_bytes(self) -> bytes:
298298

299299
# The `to_json()` method includes these fields for backwards compatibility with APIs, but since they're not
300300
# part of metadata, they should not be serialized.
301-
del json_dict['height']
302-
del json_dict['min_height']
303-
del json_dict['feature_activation_bit_counts']
301+
if 'height' in json_dict:
302+
del json_dict['height']
303+
if 'min_height' in json_dict:
304+
del json_dict['min_height']
305+
if 'feature_activation_bit_counts' in json_dict:
306+
del json_dict['feature_activation_bit_counts']
304307
# TODO: This one has not been migrated yet, but will be in the next PR
305-
# del json_dict['feature_states']
308+
# if 'feature_states' in json_dict:
309+
# del json_dict['feature_states']
306310

307311
return json_dumpb(json_dict)
308312

0 commit comments

Comments
 (0)