astreum 0.1.14__py3-none-any.whl → 0.1.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of astreum might be problematic. Click here for more details.
- astreum/lispeum/storage.py +10 -10
- astreum/lispeum/utils.py +17 -0
- astreum/node/__init__.py +333 -552
- astreum/node/relay/envelope.py +5 -5
- astreum/node/storage/__init__.py +13 -0
- astreum/node/storage/merkle.py +734 -0
- astreum/node/{models.py → storage/storage.py} +41 -99
- astreum/node/storage/trie.py +146 -0
- astreum/node/storage/utils.py +137 -0
- astreum/node/utils.py +34 -0
- astreum/node/validation/__init__.py +84 -0
- astreum/node/validation/account.py +874 -0
- astreum/node/validation/block/__init__.py +12 -0
- astreum/node/validation/block/create.py +98 -0
- astreum/node/validation/block/model.py +81 -0
- astreum/node/validation/block/validate.py +196 -0
- astreum/node/validation/constants.py +15 -0
- astreum/node/validation/stake.py +229 -0
- astreum/node/validation/state.py +230 -0
- astreum/node/validation/vdf.py +80 -0
- {astreum-0.1.14.dist-info → astreum-0.1.15.dist-info}/METADATA +2 -1
- {astreum-0.1.14.dist-info → astreum-0.1.15.dist-info}/RECORD +25 -9
- {astreum-0.1.14.dist-info → astreum-0.1.15.dist-info}/LICENSE +0 -0
- {astreum-0.1.14.dist-info → astreum-0.1.15.dist-info}/WHEEL +0 -0
- {astreum-0.1.14.dist-info → astreum-0.1.15.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Blockchain state management.
|
|
3
|
+
|
|
4
|
+
This module manages the blockchain state, including accounts, blocks, and
|
|
5
|
+
transactions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
from typing import Dict, List, Optional, Set, Any
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from ..utils import hash_data
|
|
12
|
+
from ..models import Block, Transaction, Account as ModelAccount
|
|
13
|
+
from .account import Account
|
|
14
|
+
from .constants import VALIDATION_ADDRESS, BURN_ADDRESS, MIN_STAKE_AMOUNT
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BlockchainState:
|
|
18
|
+
"""
|
|
19
|
+
Manages the state of the blockchain.
|
|
20
|
+
|
|
21
|
+
This class tracks the current state of accounts, blocks, and transactions,
|
|
22
|
+
and provides methods to update the state with new blocks and transactions.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, config: Optional[dict] = None):
|
|
26
|
+
"""
|
|
27
|
+
Initialize blockchain state.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
config: Optional configuration
|
|
31
|
+
"""
|
|
32
|
+
self.config = config or {}
|
|
33
|
+
|
|
34
|
+
# Dictionaries to track blockchain state
|
|
35
|
+
self.accounts = {} # address -> Account
|
|
36
|
+
self.blocks = {} # hash -> Block
|
|
37
|
+
self.transactions = {} # hash -> Transaction
|
|
38
|
+
|
|
39
|
+
# Track the latest block
|
|
40
|
+
self.latest_block = None
|
|
41
|
+
|
|
42
|
+
# Pending transactions
|
|
43
|
+
self.pending_transactions = set() # Set of transaction hashes
|
|
44
|
+
|
|
45
|
+
# State of validators and stakes
|
|
46
|
+
self.validators = {} # address -> stake amount
|
|
47
|
+
|
|
48
|
+
# Initialize the genesis block if not provided
|
|
49
|
+
if not self.latest_block:
|
|
50
|
+
self._initialize_genesis()
|
|
51
|
+
|
|
52
|
+
def _initialize_genesis(self):
|
|
53
|
+
"""Initialize the genesis block and state."""
|
|
54
|
+
# In a full implementation, this would initialize the genesis
|
|
55
|
+
# block and state from configuration
|
|
56
|
+
print("Initializing genesis block and state")
|
|
57
|
+
|
|
58
|
+
def add_block(self, block: Block) -> bool:
|
|
59
|
+
"""
|
|
60
|
+
Add a block to the blockchain state.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
block: Block to add
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
True if block was added successfully, False otherwise
|
|
67
|
+
"""
|
|
68
|
+
# Convert block to validation format directly
|
|
69
|
+
validation_block = {
|
|
70
|
+
'number': block.number,
|
|
71
|
+
'timestamp': block.time,
|
|
72
|
+
'producer': block.validator.public_key if block.validator else b'',
|
|
73
|
+
'previous': block.previous.get_hash() if block.previous else b'',
|
|
74
|
+
'transactions': self._extract_transactions(block),
|
|
75
|
+
'vdf_proof': block.signature[:8], # Use part of signature as VDF proof for demo
|
|
76
|
+
'signature': block.signature
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Check for duplicate (already processed) blocks
|
|
80
|
+
block_hash = block.get_hash()
|
|
81
|
+
if block_hash in self.blocks:
|
|
82
|
+
print(f"Block {block_hash.hex()} already in blockchain")
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
# Convert block's accounts to validation accounts
|
|
86
|
+
account_dict = {}
|
|
87
|
+
# Here we would deserialize the accounts data from the block
|
|
88
|
+
# In a real implementation, this would reconstruct accounts from serialized data
|
|
89
|
+
|
|
90
|
+
# For now, we'll just log that we would process the block
|
|
91
|
+
print(f"Processing block at height {block.number}")
|
|
92
|
+
|
|
93
|
+
# Add the block to our state
|
|
94
|
+
self.blocks[block_hash] = block
|
|
95
|
+
|
|
96
|
+
# Update latest block if this is a new latest block
|
|
97
|
+
if not self.latest_block or block.number > self.latest_block.number:
|
|
98
|
+
self.latest_block = block
|
|
99
|
+
|
|
100
|
+
# Process transactions in the block
|
|
101
|
+
# This would update account states, apply transaction effects, etc.
|
|
102
|
+
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
def _extract_transactions(self, block: Block) -> List[dict]:
|
|
106
|
+
"""
|
|
107
|
+
Extract transactions from a block.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
block: The model Block instance
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of transactions in validation format
|
|
114
|
+
"""
|
|
115
|
+
transactions = []
|
|
116
|
+
# Parse transaction data from the block
|
|
117
|
+
# In a real implementation, this would deserialize the transactions field
|
|
118
|
+
# For now, we'll return an empty list as a placeholder
|
|
119
|
+
return transactions
|
|
120
|
+
|
|
121
|
+
def add_transaction(self, transaction: Transaction) -> bool:
|
|
122
|
+
"""
|
|
123
|
+
Add a transaction to the pending set.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
transaction: Transaction to add
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
True if transaction was added successfully, False otherwise
|
|
130
|
+
"""
|
|
131
|
+
# Convert transaction to validation format directly
|
|
132
|
+
validation_tx = {
|
|
133
|
+
'sender': transaction.sender.public_key if transaction.sender else b'',
|
|
134
|
+
'recipient': transaction.receipient.public_key if transaction.receipient else b'',
|
|
135
|
+
'amount': transaction.amount,
|
|
136
|
+
'counter': transaction.counter,
|
|
137
|
+
'data': transaction.data,
|
|
138
|
+
'signature': transaction.signature
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# Generate a transaction hash
|
|
142
|
+
tx_hash = hash_data(str(validation_tx).encode())
|
|
143
|
+
|
|
144
|
+
# Check for duplicate transactions
|
|
145
|
+
if tx_hash in self.transactions or tx_hash in self.pending_transactions:
|
|
146
|
+
print(f"Transaction {tx_hash.hex()} already processed or pending")
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
# Validate the transaction
|
|
150
|
+
# In a real implementation, this would check signature, sender balance, etc.
|
|
151
|
+
|
|
152
|
+
# Add to pending transactions
|
|
153
|
+
self.pending_transactions.add(tx_hash)
|
|
154
|
+
|
|
155
|
+
return True
|
|
156
|
+
|
|
157
|
+
def is_staking_transaction(self, tx: Transaction) -> bool:
|
|
158
|
+
"""
|
|
159
|
+
Check if a transaction is a staking transaction.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
tx: The model Transaction instance
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
True if this is a staking transaction, False otherwise
|
|
166
|
+
"""
|
|
167
|
+
# A transaction is a staking transaction if it's sending to the validation address
|
|
168
|
+
if tx.receipient and hasattr(tx.receipient, 'public_key'):
|
|
169
|
+
return tx.receipient.public_key == VALIDATION_ADDRESS
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
def get_account(self, address: bytes) -> Optional[Account]:
|
|
173
|
+
"""
|
|
174
|
+
Get an account by address.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
address: Account address
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Account if found, None otherwise
|
|
181
|
+
"""
|
|
182
|
+
return self.accounts.get(address)
|
|
183
|
+
|
|
184
|
+
def get_validator_stake(self, address: bytes) -> int:
|
|
185
|
+
"""
|
|
186
|
+
Get the stake of a validator.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
address: Validator address
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Stake amount (0 if not a validator)
|
|
193
|
+
"""
|
|
194
|
+
return self.validators.get(address, 0)
|
|
195
|
+
|
|
196
|
+
def is_validator(self, address: bytes) -> bool:
|
|
197
|
+
"""
|
|
198
|
+
Check if an address is a validator.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
address: Address to check
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
True if address is a validator, False otherwise
|
|
205
|
+
"""
|
|
206
|
+
return self.get_validator_stake(address) >= MIN_STAKE_AMOUNT
|
|
207
|
+
|
|
208
|
+
def get_pending_transactions(self) -> List[Transaction]:
|
|
209
|
+
"""
|
|
210
|
+
Get all pending transactions.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
List of pending transactions
|
|
214
|
+
"""
|
|
215
|
+
# In a real implementation, this would return the actual transaction objects
|
|
216
|
+
# For now, we'll just return an empty list
|
|
217
|
+
return []
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def create_blockchain(config: Optional[dict] = None) -> BlockchainState:
|
|
221
|
+
"""
|
|
222
|
+
Create a new blockchain state.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
config: Optional configuration
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
New BlockchainState instance
|
|
229
|
+
"""
|
|
230
|
+
return BlockchainState(config)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Verifiable Delay Function (VDF) implementation for Astreum.
|
|
3
|
+
|
|
4
|
+
This module provides functionality for computing and verifying VDFs,
|
|
5
|
+
which are used in the consensus mechanism to create artificial time delays.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
import hashlib
|
|
10
|
+
from typing import Optional
|
|
11
|
+
from .constants import VDF_DIFFICULTY
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def compute_vdf(input_data: bytes, difficulty: Optional[int] = None) -> bytes:
|
|
15
|
+
"""
|
|
16
|
+
Compute VDF output for given input data.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
input_data: Input data for VDF
|
|
20
|
+
difficulty: VDF difficulty level (if None, uses default)
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
VDF output
|
|
24
|
+
"""
|
|
25
|
+
if difficulty is None:
|
|
26
|
+
difficulty = VDF_DIFFICULTY
|
|
27
|
+
|
|
28
|
+
# Simple VDF implementation
|
|
29
|
+
# In a real implementation, this would be a proper VDF algorithm
|
|
30
|
+
# like Wesolowski VDF or Pietrzak VDF
|
|
31
|
+
|
|
32
|
+
# For this implementation, we'll use a simple hash-based time delay
|
|
33
|
+
result = input_data
|
|
34
|
+
for _ in range(difficulty):
|
|
35
|
+
result = hashlib.sha256(result).digest()
|
|
36
|
+
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def verify_vdf(input_data: bytes, output: bytes, difficulty: Optional[int] = None) -> bool:
|
|
41
|
+
"""
|
|
42
|
+
Verify VDF computation.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
input_data: Input data for VDF
|
|
46
|
+
output: Purported VDF output
|
|
47
|
+
difficulty: VDF difficulty level (if None, uses default)
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
True if output is valid, False otherwise
|
|
51
|
+
"""
|
|
52
|
+
if difficulty is None:
|
|
53
|
+
difficulty = VDF_DIFFICULTY
|
|
54
|
+
|
|
55
|
+
# Compute expected output and compare
|
|
56
|
+
expected = compute_vdf(input_data, difficulty)
|
|
57
|
+
return expected == output
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def validate_block_vdf(block_number: int, previous_hash: bytes, vdf_proof: bytes) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Validate the VDF proof for a block.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
block_number: Block number
|
|
66
|
+
previous_hash: Hash of previous block
|
|
67
|
+
vdf_proof: VDF proof to validate
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
True if VDF proof is valid, False otherwise
|
|
71
|
+
"""
|
|
72
|
+
# Skip VDF validation for genesis block
|
|
73
|
+
if block_number == 0:
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
# Compute expected input for the VDF
|
|
77
|
+
vdf_input = previous_hash
|
|
78
|
+
|
|
79
|
+
# Verify the VDF
|
|
80
|
+
return verify_vdf(vdf_input, vdf_proof)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.15
|
|
4
4
|
Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
|
|
5
5
|
Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
|
|
6
6
|
Project-URL: Homepage, https://github.com/astreum/lib
|
|
@@ -13,6 +13,7 @@ Description-Content-Type: text/markdown
|
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: pycryptodomex==3.21.0
|
|
15
15
|
Requires-Dist: cryptography==44.0.2
|
|
16
|
+
Requires-Dist: blake3==1.0.4
|
|
16
17
|
|
|
17
18
|
# lib
|
|
18
19
|
|
|
@@ -2,8 +2,9 @@ astreum/__init__.py,sha256=di8SwGUW1lNKUwvNWCjH9eLmz__sk1SbtuNZVjjpRe4,59
|
|
|
2
2
|
astreum/lispeum/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
astreum/lispeum/expression.py,sha256=3zOEoXFHpzEFUIi1clONY55WYAh5K0YkYhaLmtvQj0I,2939
|
|
4
4
|
astreum/lispeum/parser.py,sha256=SU8mjPj1ub4xQbU4CeX15HmKZAj4vI6TeefX2B72VCo,1191
|
|
5
|
-
astreum/lispeum/storage.py,sha256=
|
|
5
|
+
astreum/lispeum/storage.py,sha256=l_ch4Ch180FvduaSe71bG4-68E8oojsZSvcsU3Eu3so,13836
|
|
6
6
|
astreum/lispeum/tokenizer.py,sha256=4obr1Jt-k1TqhsImHWUn7adQl9Ks5_VmcEFOTlQwocQ,1437
|
|
7
|
+
astreum/lispeum/utils.py,sha256=R68KoM_HdEDQeowxWJUswAgdHdwrfrlsB1zwuZURaVg,279
|
|
7
8
|
astreum/lispeum/special/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
9
|
astreum/lispeum/special/definition.py,sha256=ukQJ-Mz57bpqbTPQiwmevdE4uxec3Zt--apT3VhUYqU,840
|
|
9
10
|
astreum/lispeum/special/list/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -20,18 +21,33 @@ astreum/lispeum/special/number/addition.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
|
|
|
20
21
|
astreum/machine/__init__.py,sha256=GOdZl1tS9uIJHbq5WVcplifMDPDLQroX7CVew-K2YbA,15262
|
|
21
22
|
astreum/machine/environment.py,sha256=K0084U6B7wwjrDZ9b2_7cEcbBzsB7UOy_Zpbrr7B3GY,834
|
|
22
23
|
astreum/machine/error.py,sha256=MvqBaZZt33rNELNhUJ2lER3TE3aS8WVqsWF2hz2AwoA,38
|
|
23
|
-
astreum/node/__init__.py,sha256=
|
|
24
|
-
astreum/node/
|
|
24
|
+
astreum/node/__init__.py,sha256=K7F21c7V9vlJuTWoSw9arsLA8RF5KNR5I6QS3Frl_Ys,19481
|
|
25
|
+
astreum/node/utils.py,sha256=amGhNYHVMjvAO-9vBRAcim-S5LlLSRudqooBN-XPdm4,702
|
|
25
26
|
astreum/node/relay/__init__.py,sha256=0zvbchIbLUPqGA7QXJbXokKupcIq6Iu3X3VUpxejIQc,14981
|
|
26
27
|
astreum/node/relay/bucket.py,sha256=pcmollbbM-xeHlmDxLZnzvf0Ut-9v9RoN6SijYiQuu8,2893
|
|
27
|
-
astreum/node/relay/envelope.py,sha256=
|
|
28
|
+
astreum/node/relay/envelope.py,sha256=sDKsIvJruQKLWgWs92sx1mCjMHF7yQVoLguPygw2Pz8,10037
|
|
28
29
|
astreum/node/relay/message.py,sha256=uezmGjNaQK4fZmYQLCHd2YpiosaaFb8DOa3H58HS1jA,2887
|
|
29
30
|
astreum/node/relay/peer.py,sha256=DlvTR9j0BZQ1dW-p_9UGgfLvQqwNdpNLMSCYEW4FhyI,5899
|
|
30
31
|
astreum/node/relay/route.py,sha256=fyOSsAe1mfsCVeN6LtQ_OEUEb1FiC5dobZBEJKNGU9U,5814
|
|
32
|
+
astreum/node/storage/__init__.py,sha256=eJL7ILUGKSKDhXZqkn3Rk5TUza39vIcwPwAW_AaIHTk,326
|
|
33
|
+
astreum/node/storage/merkle.py,sha256=gZf18ZhaJffckmuQbc92MwfxepBLwINdORuM6EeyJBc,23879
|
|
34
|
+
astreum/node/storage/storage.py,sha256=czJDhRK2rQxjOo88fhZ6j10f55RW8hWp1qq7c4yur6Y,10086
|
|
35
|
+
astreum/node/storage/trie.py,sha256=DrYNEgTS6j7WL-LtVzzCIDjSmjphjV-jL1bGXBlcyfQ,4930
|
|
36
|
+
astreum/node/storage/utils.py,sha256=CxKH9GbW31aVYs2Hwg1SirCykSnH_3_JisEayDrpOvY,5169
|
|
37
|
+
astreum/node/validation/__init__.py,sha256=D9Gmc5x5V5xgXtM32axuDMSzgKiDCz7MiUShNnuFKYc,1636
|
|
38
|
+
astreum/node/validation/account.py,sha256=gFoJHoSAZs7BKGaewNaLiziivKINTCh8MBpOoN-YajA,27951
|
|
39
|
+
astreum/node/validation/constants.py,sha256=ImIdLZFtMKx1iWg60YssEKl2tdDqZQnIa2JaJE6CX0o,422
|
|
40
|
+
astreum/node/validation/stake.py,sha256=Z9EPM-X9c92fpsZIYsdVpvgz4DhxQViPM-RDktWUZq8,7141
|
|
41
|
+
astreum/node/validation/state.py,sha256=QMXY7h4da-o79wMjJW93ixtepyhgcbJEf101yVuZurg,7661
|
|
42
|
+
astreum/node/validation/vdf.py,sha256=HDdnqn9O_LicfE7SNCmncawKoR-ojLyjlpn74OvRNOU,2194
|
|
43
|
+
astreum/node/validation/block/__init__.py,sha256=n2HaMG_5cpa7y5xko-c7vbHz8rL-1wAGthmr8boNrCs,216
|
|
44
|
+
astreum/node/validation/block/create.py,sha256=apD9h92b9Y146zeppSzKNk_NJPgyBy7FhxoEkypQQNk,2515
|
|
45
|
+
astreum/node/validation/block/model.py,sha256=d7x3_tX2MPLnGODL_5_vNjQfLdYa405blc-zL9o8HFA,2589
|
|
46
|
+
astreum/node/validation/block/validate.py,sha256=niLexCNhEwUJLclyrdNZSvHcVa_J6jlu7J3FiWY7XBU,6232
|
|
31
47
|
astreum/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
48
|
astreum/utils/bytes_format.py,sha256=X4tG5GGPweNCE54bHYkLFiuLTbmpy5upO_s1Cef-MGA,2711
|
|
33
|
-
astreum-0.1.
|
|
34
|
-
astreum-0.1.
|
|
35
|
-
astreum-0.1.
|
|
36
|
-
astreum-0.1.
|
|
37
|
-
astreum-0.1.
|
|
49
|
+
astreum-0.1.15.dist-info/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
|
|
50
|
+
astreum-0.1.15.dist-info/METADATA,sha256=dkXfuFNHZTgGlPpIHmcYC8hReofWF52gD_4_6JcCQSA,3290
|
|
51
|
+
astreum-0.1.15.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
52
|
+
astreum-0.1.15.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
|
|
53
|
+
astreum-0.1.15.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|