Skip to content

Commit 830a7d9

Browse files
author
Matthias Zimmermann
committed
feat: rename btl into expires_in
1 parent 8d6e041 commit 830a7d9

38 files changed

+336
-470
lines changed

PUBLISHING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ Python session in interactive shell.
192192
```python
193193
from arkiv import Arkiv
194194
client = Arkiv()
195-
entity_key, _ = client.arkiv.create_entity(payload=b'Hello world!', btl=1000)
195+
entity_key, _ = client.arkiv.create_entity(payload=b'Hello world!', expires_in=1000)
196196
client.arkiv.get_entity(entity_key)
197197
```
198198

README.md

Lines changed: 3 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ entity_key, receipt = client.arkiv.create_entity(
4141
payload=b"Hello World!",
4242
content_type="text/plain",
4343
attributes={"type": "greeting", "version": 1},
44-
btl=1000
44+
expires_in=1000
4545
)
4646

4747
# Check and print entity key
@@ -71,7 +71,7 @@ async def main():
7171
entity_key, tx_hash = await client.arkiv.create_entity(
7272
payload=b"Hello Async World!",
7373
attributes={"type": "greeting", "version": 1},
74-
btl=1000
74+
expires_in=1000
7575
)
7676

7777
# Get entity and check existence
@@ -110,7 +110,7 @@ client = Arkiv(provider, account=account)
110110
entity_key, tx_hash = client.arkiv.create_entity(
111111
payload=b"Hello World!",
112112
attributes={"type": "greeting", "version": 1},
113-
btl = 1000
113+
expires_in = 1000
114114
)
115115

116116
entity = client.arkiv.get_entity(entity_key)
@@ -398,60 +398,6 @@ client.arkiv.cleanup_filters()
398398

399399
## Arkiv Topics/Features
400400

401-
### BTL
402-
403-
BTL (Blocks-To-Live) should be replaced with explicit `expires_at_block` values for predictability and composability.
404-
405-
Relative `BTL` depends on execution timing and creates unnecessary complexity:
406-
- An entity created with `btl=100` will have different expiration blocks depending on when the transaction is mined
407-
- Extending entity lifetimes requires fetching the entity, calculating remaining blocks, and adding more—a race-prone pattern
408-
- Creates asymmetry between write operations (which use `btl`) and read operations (which return `expires_at_block`)
409-
- Mempool management can accept any value
410-
411-
Absolute `expires_at_block` is predictable, composable, and matches what you get when reading entities:
412-
- Deterministic regardless of execution timing
413-
- Maps directly to `Entity.expires_at_block` field returned by queries
414-
- Enables clean compositional patterns like `replace(entity, expires_at_block=entity.expires_at_block + 100)`
415-
- Aligns write API with read API, making the SDK more intuitive
416-
- Mempool needs to manage/delete tx with expires at values in the past
417-
418-
It's complicated. Deterministic mempool management vs predictable tx management in mempool.
419-
420-
```python
421-
from dataclasses import replace
422-
423-
# Fetch entity
424-
entity = client.arkiv.get_entity(entity_key)
425-
426-
# Modify payload and extend expiration by 100 blocks
427-
updated_entity = replace(
428-
entity,
429-
payload=b"new data",
430-
expires_at_block=entity.expires_at_block + 100
431-
)
432-
433-
# Update entity
434-
client.arkiv.update_entity(updated_entity)
435-
```
436-
437-
### Sorting
438-
439-
Querying entities should support sorting results by one or more fields.
440-
441-
**Requirements:**
442-
- Sort by attributes (string and numeric)
443-
- Sort by metadata (owner, expires_at_block)
444-
- Support ascending and descending order
445-
- Multi-field sorting with priority
446-
447-
**Example:**
448-
```python
449-
# SQL-style sorting
450-
results = client.arkiv.query(
451-
"SELECT * FROM entities ORDER BY attributes.priority DESC, attributes.name ASC"
452-
)
453-
```
454-
455401
### Other Features
456402

457403
- **Creation Flags**: Entities should support creation-time flags with meaningful defaults.

docs/MARKETING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ Each record ("entity") contains:
348348
### **Poor Use Cases (Don't Market These)**
349349

350350
1.**Permanent Document Archive**
351-
- Why: All data expires via BTL, not suitable for permanent legal records
351+
- Why: All data expires via expires_in, not suitable for permanent legal records
352352
- Alternative: Extend important entities regularly, but acknowledge the maintenance
353353

354354
2.**Real-Time Database Replacement**

docs/USE_CASES.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ entity_key, tx_hash = client.arkiv.create_entity(
3030
"trait_type": "beanie",
3131
"trait_eyes": "blue"
3232
},
33-
btl=100_000_000 # a few years with 2s blocks
33+
expires_in=100_000_000 # a few years with 2s blocks
3434
)
3535

3636
# NFT contract points to entity_key
@@ -82,7 +82,7 @@ for i in range(0, len(file_data), CHUNK_SIZE):
8282
chunk_key, _ = client.arkiv.create_entity(
8383
payload=file_data[i:i + CHUNK_SIZE],
8484
attributes={"file_hash": file_hash, "chunk_index": i // CHUNK_SIZE},
85-
btl=100_000_000
85+
expires_in=100_000_000
8686
)
8787
chunk_keys.append(chunk_key)
8888

@@ -94,7 +94,7 @@ manifest_key, _ = client.arkiv.create_entity(
9494
"file_name": "video.mp4",
9595
"chunk_keys": ",".join(chunk_keys),
9696
},
97-
btl=100_000_000
97+
expires_in=100_000_000
9898
)
9999

100100
# Reconstruct file
@@ -243,15 +243,15 @@ proposal = client.arkiv.create_entity(
243243
"votes_against": 0,
244244
"quorum": 40000000
245245
},
246-
btl=100_000_000 # Keep for historical record
246+
expires_in=100_000_000 # Keep for historical record
247247
)
248248

249249
# Update vote counts (cheap!)
250250
arkiv.update_entity(
251251
proposal_key,
252252
payload=proposal.payload, # Same text
253253
attributes={"votes_for": 1234, "votes_against": 567, "status": "passed"},
254-
btl=100_000_000 # Keep for historical record
254+
expires_in=100_000_000 # Keep for historical record
255255
)
256256

257257
# Query all active proposals
@@ -300,7 +300,7 @@ btc_price_data = client.arkiv.create_entity(
300300
"network_id": network_id,
301301
"market": "crypto"
302302
},
303-
btl=100_000 # Keep 1 month
303+
expires_in=100_000 # Keep 1 month
304304
)
305305

306306
# Applications read latest or query history
@@ -342,7 +342,7 @@ shipment_key = client.arkiv.create_entity(
342342
"product_sku": "PROD-789",
343343
"status": "in_transit",
344344
},
345-
btl=10_000_000 # Keep 6 months
345+
expires_in=10_000_000 # Keep 6 months
346346
)
347347

348348
status_key = client.arkiv.create_entity(
@@ -354,7 +354,7 @@ status_key = client.arkiv.create_entity(
354354
"location": "warehouse_b",
355355
"temperature": 5.0,
356356
}
357-
btl=10_000_000 # Keep 6 months
357+
expires_in=10_000_000 # Keep 6 months
358358
)
359359

360360
delivery_key = client.arkiv.create_entity(
@@ -368,7 +368,7 @@ delivery_key = client.arkiv.create_entity(
368368
"delivery_timestamp": delivery_timestamp,
369369
"signature": customer_signature_hash,
370370
}
371-
btl=10_000_000 # Keep 6 months
371+
expires_in=10_000_000 # Keep 6 months
372372
)
373373

374374
# Update status at each checkpoint
@@ -430,7 +430,7 @@ model_entity_key = client.arkiv.create_entity(
430430
"author": researcher_address,
431431
"framework": "pytorch"
432432
},
433-
btl=10_000_000
433+
expires_in=10_000_000
434434
)
435435

436436
# Track experiments
@@ -446,7 +446,7 @@ experiment = client.arkiv.create_entity(
446446
"accuracy": 95,
447447
"f1_score": 93,
448448
},
449-
btl=5_000_000
449+
expires_in=5_000_000
450450
)
451451

452452
# Provable model lineage

src/arkiv/contract.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99
from web3.types import RPCEndpoint
1010

1111
# ArkivProcessorAddress
12-
STORAGE_ADDRESS: Final[ChecksumAddress] = Web3.to_checksum_address(
12+
ARKIV_ADDRESS: Final[ChecksumAddress] = Web3.to_checksum_address(
1313
"0x00000000000000000000000000000061726B6976"
1414
)
1515

16-
1716
CREATED_EVENT: Final[str] = "ArkivEntityCreated"
1817
UPDATED_EVENT: Final[str] = "ArkivEntityUpdated"
1918
EXPIRED_EVENT: Final[str] = "ArkivEntityExpired"

src/arkiv/module.py

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,16 @@ def create_entity(
7575
payload: bytes | None = None,
7676
content_type: str | None = None,
7777
attributes: Attributes | None = None,
78-
btl: int | None = None,
78+
expires_in: int | None = None,
7979
tx_params: TxParams | None = None,
8080
) -> tuple[EntityKey, TransactionReceipt]:
8181
# Docstring inherited from ArkivModuleBase.create_entity
8282
# Create operation and execute TX
8383
create_op = to_create_op(
84-
payload=payload, content_type=content_type, attributes=attributes, btl=btl
84+
payload=payload,
85+
content_type=content_type,
86+
attributes=attributes,
87+
expires_in=expires_in,
8588
)
8689
operations = Operations(creates=[create_op])
8790
receipt = self.execute(operations, tx_params)
@@ -100,7 +103,7 @@ def update_entity(
100103
payload: bytes | None = None,
101104
content_type: str | None = None,
102105
attributes: Attributes | None = None,
103-
btl: int | None = None,
106+
expires_in: int | None = None,
104107
tx_params: TxParams | None = None,
105108
) -> TransactionReceipt:
106109
# Docstring inherited from ArkivModuleBase.update_entity
@@ -110,7 +113,7 @@ def update_entity(
110113
payload=payload,
111114
content_type=content_type,
112115
attributes=attributes,
113-
btl=btl,
116+
expires_in=expires_in,
114117
)
115118
operations = Operations(updates=[update_op])
116119
receipt = self.execute(operations, tx_params)
@@ -403,33 +406,6 @@ def get_block_timing(self) -> Any:
403406

404407
return block_timing_response
405408

406-
def to_blocks(
407-
self, seconds: int = 0, minutes: int = 0, hours: int = 0, days: int = 0
408-
) -> int:
409-
"""
410-
Convert a time duration to number of blocks.
411-
412-
Useful for calculating blocks-to-live (BTL) parameters based on
413-
desired entity lifetime.
414-
415-
Args:
416-
seconds: Number of seconds
417-
minutes: Number of minutes
418-
hours: Number of hours
419-
days: Number of days
420-
421-
Returns:
422-
Number of blocks corresponding to the time duration
423-
"""
424-
total_seconds = seconds + minutes * 60 + hours * 3600 + days * 86400
425-
426-
if not hasattr(self, "_block_duration"):
427-
block_timing = self.get_block_timing()
428-
self._block_duration = block_timing["duration"]
429-
430-
block_time = self._block_duration
431-
return total_seconds // block_time if block_time else 0
432-
433409
@property
434410
def active_filters(self) -> list[EventFilter]:
435411
"""Get a copy of currently active event filters."""

src/arkiv/module_async.py

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,16 @@ async def create_entity( # type: ignore[override]
7474
payload: bytes | None = None,
7575
content_type: str | None = None,
7676
attributes: Attributes | None = None,
77-
btl: int | None = None,
77+
expires_in: int | None = None,
7878
tx_params: TxParams | None = None,
7979
) -> tuple[EntityKey, TransactionReceipt]:
8080
# Docstring inherited from ArkivModuleBase.create_entity
8181
# Create the operation and execute TX
8282
create_op = to_create_op(
83-
payload=payload, content_type=content_type, attributes=attributes, btl=btl
83+
payload=payload,
84+
content_type=content_type,
85+
attributes=attributes,
86+
expires_in=expires_in,
8487
)
8588
operations = Operations(creates=[create_op])
8689
receipt = await self.execute(operations, tx_params)
@@ -99,7 +102,7 @@ async def update_entity( # type: ignore[override]
99102
payload: bytes | None = None,
100103
content_type: str | None = None,
101104
attributes: Attributes | None = None,
102-
btl: int | None = None,
105+
expires_in: int | None = None,
103106
tx_params: TxParams | None = None,
104107
) -> TransactionReceipt:
105108
# Docstring inherited from ArkivModuleBase.update_entity
@@ -109,7 +112,7 @@ async def update_entity( # type: ignore[override]
109112
payload=payload,
110113
content_type=content_type,
111114
attributes=attributes,
112-
btl=btl,
115+
expires_in=expires_in,
113116
)
114117
operations = Operations(updates=[update_op])
115118
receipt = await self.execute(operations, tx_params)
@@ -366,33 +369,6 @@ async def get_block_timing(self) -> Any:
366369

367370
return block_timing_response
368371

369-
async def to_blocks(
370-
self, seconds: int = 0, minutes: int = 0, hours: int = 0, days: int = 0
371-
) -> int:
372-
"""
373-
Convert a time duration to number of blocks.
374-
375-
Useful for calculating blocks-to-live (BTL) parameters based on
376-
desired entity lifetime.
377-
378-
Args:
379-
seconds: Number of seconds
380-
minutes: Number of minutes
381-
hours: Number of hours
382-
days: Number of days
383-
384-
Returns:
385-
Number of blocks corresponding to the time duration
386-
"""
387-
total_seconds = seconds + minutes * 60 + hours * 3600 + days * 86400
388-
389-
if not hasattr(self, "_block_duration"):
390-
block_timing = await self.get_block_timing()
391-
self._block_duration = block_timing["duration"]
392-
393-
block_time = self._block_duration
394-
return total_seconds // block_time if block_time else 0
395-
396372
@property
397373
def active_filters(self) -> list[AsyncEventFilter]:
398374
"""Get a copy of currently active async event filters."""

0 commit comments

Comments
 (0)