Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow patch state to take in multiple patches + access key and account patching #124

Open
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

adsick
Copy link

@adsick adsick commented Apr 21, 2022

Hi, I'm working on #12 and #19. Now I feel like some review of what I've done would be great, please tell me what API is preferable and how to shape it (where to put stuff etc.).

Highlights:

    pub async fn patch_state(
        &self,
        contract_id: &AccountId,
        key: impl Into<Vec<u8>>,
        value: impl Into<Vec<u8>>,
    )
    pub async fn patch_state_multiple(
        &self,
        account_id: &AccountId,
        kvs: impl IntoIterator<Item = (impl Into<Vec<u8>>, impl Into<Vec<u8>>)>,
    )
    /// Patch account state using builder pattern
    pub fn patch_account(&self, account_id: &AccountId) -> SandboxPatchStateAccountBuilder {
        self.workspace.patch_account(account_id.clone())
    }

bonus: fixed long cargo check/build times issue by not installing the node every time - the issue was in the build.rs

Now I'm looking for some feedback to refine API 🙂

Copy link
Member

@ChaoticTempest ChaoticTempest left a comment

Choose a reason for hiding this comment

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

Thanks so much for getting this implementation in! Did a cursory look and left a few comments so we can improve the maintainability of it

workspaces/src/network/mod.rs Outdated Show resolved Hide resolved
workspaces/src/network/mod.rs Outdated Show resolved Hide resolved
Comment on lines 4 to 7
// using unwrap because all the useful error messages are hidden inside
near_sandbox_utils::ensure_sandbox_bin().unwrap();
// previously the next line was causing near sandbox to be installed every time cargo build/check was called
// near_sandbox_utils::install().expect("Could not install sandbox");
Copy link
Member

Choose a reason for hiding this comment

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

nice, but can you remove the comments surrounding it and also change the unwrap to an expect, since the error messages can be confusing at times when they don't give context:

Suggested change
// using unwrap because all the useful error messages are hidden inside
near_sandbox_utils::ensure_sandbox_bin().unwrap();
// previously the next line was causing near sandbox to be installed every time cargo build/check was called
// near_sandbox_utils::install().expect("Could not install sandbox");
near_sandbox_utils::ensure_sandbox_bin().expect("Could not install sandbox");

Copy link
Author

Choose a reason for hiding this comment

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

Yes, I agree about the context part, but there also should be a note of 'precise' reason of failure.
I'll try to do something like "Could not install sandbox. Reason: ... "
That sounds good?

Copy link
Member

Choose a reason for hiding this comment

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

So after #134 we won't be able to change it to ensure_sandbox_bin(), but to alleviate this issue with re-installing, we'll also resolve it along with #88. It just requires a more involved versioning mechanism. Sounds good to you?

Comment on lines 142 to 150
pub(crate) fn patch_state(&self, account_id: AccountId) -> SandboxPatchStateBuilder {
SandboxPatchStateBuilder::new(self, account_id)
}

pub(crate) fn patch_account(&self, account_id: AccountId) -> SandboxPatchStateAccountBuilder {
SandboxPatchStateAccountBuilder::new(self, account_id)
}

pub(crate) fn patch_access_key(
Copy link
Member

Choose a reason for hiding this comment

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

can you also include some tests for these various patches just so we can get some confidence that they're working as expected. You can probably reuse a bunch of the test code found in tests/patch_state.rs

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, we didn't tested because we waited for the feedback. Tests will be added too.

workspaces/src/network/sandbox.rs Show resolved Hide resolved
Comment on lines 212 to 216
pub async fn patch_state_multiple(
&self,
account_id: &AccountId,
kvs: impl IntoIterator<Item = (impl Into<Vec<u8>>, impl Into<Vec<u8>>)>,
) -> anyhow::Result<()> {
Copy link
Member

Choose a reason for hiding this comment

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

I don't know if I'm a fan of taking an IntoIterator as a parameter, but this might be the only way I can think of doing this.

@matklad @austinabell, would you have to know of anything better than this? Or if there's a better API design than having a separate function just be for the iterator

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah, for the same rationale as #124 (comment) but primarily that the API will transition away from requiring owned Vec<u8> and will be a breaking change down the road to change. Prob best to constrain to &[u8] for now

Copy link
Contributor

Choose a reason for hiding this comment

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

You want impl std::iter::Extend here.

workspaces/src/network/sandbox.rs Outdated Show resolved Hide resolved
// self
// }

pub async fn apply(self) -> anyhow::Result<()> {
Copy link
Member

Choose a reason for hiding this comment

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

rename the function to transact to be more consistent with other Transaction objects

Suggested change
pub async fn apply(self) -> anyhow::Result<()> {
pub async fn transact(self) -> anyhow::Result<()> {

Comment on lines 198 to 199
key: impl Into<Vec<u8>>,
value: impl Into<Vec<u8>>,
Copy link
Member

Choose a reason for hiding this comment

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

not everything will impl an Into<Vec<u8>> and by that notion won’t fit all cases. Best to keep as-is to avoid that situation, since in rust a lot of things can be converted into a series of bytes &[u8] including Vec<u8>. This will also avoid having to make a breaking change for this since now people would have to require this trait impl.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah these traits are not as intuitive for a developer to read as well as it sticks us with using this API even when we remove the requirement for bytes to be converted to Vec<u8>

/// Patch state into the sandbox network, using builder pattern. This will allow us to set
/// state that we have acquired in some manner. This allows us to test random cases that
/// are hard to come up naturally as state evolves.
pub fn patch_state_builder(&self, account_id: &AccountId) -> SandboxPatchStateBuilder {
Copy link
Member

Choose a reason for hiding this comment

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

Think it's better just to have one patch_state function that way we can be consistent and have a defacto way of doing patching state. Let's merge this one and the other and just have this builder like one be the default

Copy link
Author

Choose a reason for hiding this comment

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

Let's merge this one and the other

which 'other' one?

Copy link
Member

Choose a reason for hiding this comment

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

I meant we can merge both patch_state and patch_state_builder to be just patch_state(AccountId) -> Builder. Would be a breaking API change, but it's fine since it would align with the other builder functions, and better to be consistent that way

}
}

pub fn data(mut self, key: impl Into<Vec<u8>>, value: impl Into<Vec<u8>>) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

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

impl Into<Vec<u8>> looks weird. Is there some precedent for such API shape? I'd just use Vec<u8> or &[u8], with a strong preference for &[u8].

Vec<u8> leaks impl details and forces needless allocation -- ideally, we don't store intermediate vecs at all and just build request string.

Comment on lines 200 to 215
pub fn data_multiple(
mut self,
kvs: impl IntoIterator<Item = (impl Into<Vec<u8>>, impl Into<Vec<u8>>)>,
) -> Self {
let Self {
ref mut records,
ref account_id,
..
} = self;
records.extend(kvs.into_iter().map(|(key, value)| StateRecord::Data {
account_id: account_id.clone(),
data_key: key.into(),
value: value.into(),
}));
self
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd rather let the caller to call .data in a loop. If we need multi-item API, I'd go for

impl std::iter::Extend<(&'_ [u8], &'_ [u8])> for SandboxPatchStateBuilder<'_> {

}

Copy link
Author

@adsick adsick May 6, 2022

Choose a reason for hiding this comment

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

I agree with calling data in a loop (it does not make it ergonomic for cases where you have a vec of values tho), but I don't understand why this impl std::iter::Extend<(&'_ [u8], &'_ [u8])> is better than my .data_multiple - while being a little more rust-idiomatic it does not allowing it to be called in a builder chain and looks imo worse than .data_multiple

Copy link
Contributor

Choose a reason for hiding this comment

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

good point about the builder!

Indeed, to look at the std, theer's

Though, in this case, data_multiple looks like a particualr awkward name (data itself is plural).

Maybe we want .entry and .entries here?

I'll delegate to you/dev-platform folks to make a judgement call here :)

Copy link
Member

Choose a reason for hiding this comment

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

+1 for .entry and .entries

Copy link
Author

Choose a reason for hiding this comment

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

+1 for .entry and .entries

got it

}
}

pub fn data(mut self, key: impl Into<Vec<u8>>, value: impl Into<Vec<u8>>) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like we should have a way to delete key as well?

Comment on lines 209 to 213
records.extend(kvs.into_iter().map(|(key, value)| StateRecord::Data {
account_id: account_id.clone(),
data_key: key.into(),
value: value.into(),
}));
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor: there's logical duplication between the two methods, one could delegate to the other (or both could delegate to internal _state_record)

Comment on lines 212 to 216
pub async fn patch_state_multiple(
&self,
account_id: &AccountId,
kvs: impl IntoIterator<Item = (impl Into<Vec<u8>>, impl Into<Vec<u8>>)>,
) -> anyhow::Result<()> {
Copy link
Contributor

Choose a reason for hiding this comment

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

You want impl std::iter::Extend here.

@adsick
Copy link
Author

adsick commented May 3, 2022

Thanks a bunch for the feedback, @matklad 's guidance is looking great. This week we'll try to update this PR accordingly and then start testing when that's ok in terms of API and stuff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants