Skip to content

feat(nano): implement lazy imports#1384

Open
glevco wants to merge 3 commits intomasterfrom
feat/nano/lazy-imports
Open

feat(nano): implement lazy imports#1384
glevco wants to merge 3 commits intomasterfrom
feat/nano/lazy-imports

Conversation

@glevco
Copy link
Contributor

@glevco glevco commented Aug 25, 2025

Motivation

Implement support for lazy imports in blueprints, that is, imports that depend on a runtime Runner. This will enable us to remove syscalls and provide standalone functions instead, improving developer experience.

For now, the only the get_contract syscall is converted to the new API, but in other PRs we can also make other improvements such as changing the ctx argument to a get_context() function, for example. All syscalls could be moved to this new system, allowing us to remove the unintuitive syscall attribute from blueprints.

# Before
self.syscall.get_contract(other_id, blueprint_id=None)

# After
get_contract(other_id, blueprint_id=None)

Review Notes

I recommend reviewing files in the following order:

  1. Start with lazy_import.py to understand how lazy imports work.
  2. Review custom_builtins.py to see how the Runner is injected in lazy imports during contract execution. The diff is very large because the EXEC_BUILTINS constant is moved to a function, use the "Hide whitespace" feature of GitHub to make it way smaller.
  3. Review contract_accessor.py to see the first usage of a lazy import, the get_contract function.
  4. Review all other code, which is just passing the Runner around so it can be injected in lazy imports. This is done in the exec step during class load, not in the call step.

Acceptance Criteria

  • Implement support for lazy imports in blueprints, which are imported functions that depend on a Runner.
  • Adapt the custom __import__ function to inject a Runner in lazy imports.
    • This required moving the EXEC_BUILTINS constant to a function, because now it depends on a Runner.
  • Implement get_contract function to replace the syscall.
    • The new API is currently not supported on builtin blueprints.
  • Implement test scaffolding to support the new API on tests via either blueprint files or ad-hoc classes.
    • Update the test_types_across_contracts.py test to use the new API, as a demo.

Checklist

  • If you are requesting a merge into master, confirm this code is production-ready and can be included in future releases as soon as it gets merged

@glevco glevco self-assigned this Aug 25, 2025
@glevco glevco force-pushed the feat/nano/contract-accessor branch from 5d7e920 to c018c90 Compare August 25, 2025 17:14
@glevco glevco force-pushed the feat/nano/lazy-imports branch from b3c4095 to 616fd6f Compare August 25, 2025 17:14
@glevco glevco changed the title feat(nano): implement contract accessor feat(nano): implement lazy imports Aug 25, 2025
@glevco glevco force-pushed the feat/nano/lazy-imports branch from 616fd6f to 07fbe31 Compare August 25, 2025 17:16
mutable_props.extend(search_writeable_properties(self, 'self'))
mutable_props.extend(search_writeable_properties(ctx, 'ctx'))
custom_import = EXEC_BUILTINS['__import__']
builtins = get_exec_builtins(runner=None) # TODO: This does not cover the lazy imports
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Fix this test after the design is approved.

from tests.nanocontracts.blueprints.unittest import BlueprintTestCase


@pytest.mark.skip # TODO: Fix this test
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Fix this test after the design is approved.

@codecov
Copy link

codecov bot commented Aug 25, 2025

Codecov Report

❌ Patch coverage is 98.30508% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 85.81%. Comparing base (2960f81) to head (52a9956).

Files with missing lines Patch % Lines
hathor/nanocontracts/contract_accessor.py 90.00% 1 Missing ⚠️
Additional details and impacted files
@@                       Coverage Diff                       @@
##           feat/nano/contract-accessor    #1384      +/-   ##
===============================================================
+ Coverage                        85.73%   85.81%   +0.08%     
===============================================================
  Files                              430      431       +1     
  Lines                            32561    32594      +33     
  Branches                          5081     5083       +2     
===============================================================
+ Hits                             27915    27970      +55     
+ Misses                            3621     3605      -16     
+ Partials                          1025     1019       -6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@glevco glevco force-pushed the feat/nano/contract-accessor branch from c018c90 to c02679a Compare August 25, 2025 21:53
@glevco glevco moved this from Todo to In Progress (WIP) in Hathor Network Aug 26, 2025
@glevco glevco force-pushed the feat/nano/lazy-imports branch 3 times, most recently from dd10f72 to 5bed5a6 Compare August 26, 2025 15:01
# Conflicts:
#	tests/nanocontracts/blueprints/unittest.py
@glevco glevco force-pushed the feat/nano/contract-accessor branch from c02679a to ba2ee51 Compare August 26, 2025 15:23
@glevco glevco force-pushed the feat/nano/lazy-imports branch from 5bed5a6 to 52a9956 Compare August 26, 2025 15:39
@glevco glevco moved this from In Progress (WIP) to In Progress (Done) in Hathor Network Aug 26, 2025
Copy link
Member

@jansegre jansegre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the interface and the implementation seems correct, even with important details like OnChainBlueprint.get_blueprint_class.

However, I think there are still a few problems:

  • get_contract won't work for built-in Blueprints, it's not a deal-breaker IMO, but I think we should just drop support for built-in Blueprints before doing this, which would be a separate discussion
  • I believe there might still be problems stemming from the fact that OnChainBlueprint.get_blueprint_class returns different objects for different runners or for None, even though it is the "correct" behavior, I think these problems would be solved, or rather, the structure to solve them would have to be in place first, when we have Blueprints' execution isolated in a separate process, so we might want to wait for that first. But I'll explain here.

Currently the deserializer classes of some types (most notably NamedTuple classes and any container combination with one, there might be other types, I'd have to check), have a reference to the class that is being deserialized, in order to be able to instantiate objects of that class. This means the deserializer depends on the "implementation class" that is defined in the Blueprint module, which means that different "loads" of that module will produce different "class objects", which means, that an object that was deserialized by some parsing OnChainBlueprint.get_blueprint_class(None).method will different than one deserialized by parsing OnChainBlueprint.get_blueprint_class(runner).method, this difference would have an effect on isinstance(some_tuple, MyNamedTuple) but also in other cases.

The Blueprint code that causes this situation might not be as niche as it sounds, we allow and support custom named-tuples classes, and they are being used, and we do support use of isinstance.

It's possible that the current code never mixes any objects from different types of "blueprint loads" (with different runners, or with runners and with None). But it's at least complex to verify that.

My point is that making the "loading of a blueprint's module" depend on a "runner object" is not good when we use the blueprint class (and this the objects created by the module), in the same runtime as the node's code. If we already had the execution isolated in a separate process, this would be a lot more trivial to trust. So, I'm currently leaning more towards that, even though I much prefer the DX of using get_contract instead of self.sysctl.get_contract.

@glevco glevco moved this from In Progress (Done) to In Review (WIP) in Hathor Network Aug 27, 2025
@glevco glevco force-pushed the feat/nano/contract-accessor branch from 2960f81 to 6166f29 Compare August 29, 2025 19:41
Base automatically changed from feat/nano/contract-accessor to master August 29, 2025 21:28
@glevco glevco moved this from In Review (WIP) to In Progress (WIP) in Hathor Network Oct 15, 2025
@glevco glevco moved this from In Progress (WIP) to Todo in Hathor Network Jan 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

2 participants