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

Add Wasm Account Storage interface #597

Merged
merged 148 commits into from Feb 22, 2022

Conversation

HenriqueNogara
Copy link
Contributor

@HenriqueNogara HenriqueNogara commented Jan 17, 2022

Description of change

Expose Storage trait in the bindings.

Links to any relevant issues

Type of change

  • Bug fix (a non-breaking change which fixes an issue)
  • Enhancement (a non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation Fix

How the change has been tested

Change checklist

  • I have followed the contribution guidelines for this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

@HenriqueNogara HenriqueNogara added Enhancement New feature or improvement to an existing feature Wasm Related to Wasm bindings. Becomes part of the Wasm changelog Added A new feature that requires a minor release. Part of "Added" section in changelog labels Jan 17, 2022
@HenriqueNogara HenriqueNogara changed the base branch from feat/wasm-bindings-account to epic/wasm-bindings-account February 18, 2022 13:48
Copy link
Contributor

@PhilippGackstatter PhilippGackstatter left a comment

Choose a reason for hiding this comment

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

Thanks for the memory store implementation in TS!

I still have two primary concerns.

  • There are still a lot of types that seem unnecessarily exposed. On the flipside, I'm concerned we might be missing places where the types that appear in the storage trait do not have (de)serialization functions exposed. Ideally, we would verify that these types can all be (de)serialized in the memory_storage.ts implementation, by turning it into a file store, or something like that. In-memory by itself also seems fine, as long as all types go through serialization at some point.

  • The other bigger concern is around documentation of functions, mostly relating to stating the base58 requirement, and if we actually want to keep that, since it requires lots of encoding from implementors, while we could just as easily expose these things as bytes.

bindings/wasm/Cargo.toml Outdated Show resolved Hide resolved
bindings/wasm/examples-account/src/memory_storage.ts Outdated Show resolved Hide resolved
bindings/wasm/examples-account/src/memory_storage.ts Outdated Show resolved Hide resolved
bindings/wasm/examples-account/src/memory_storage.ts Outdated Show resolved Hide resolved
bindings/wasm/examples-account/src/memory_storage.ts Outdated Show resolved Hide resolved
Comment on lines +26 to +27
#[cfg_attr(not(feature = "wasm"), async_trait)]
#[cfg_attr(feature = "wasm", async_trait(?Send))]
Copy link
Contributor

Choose a reason for hiding this comment

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

I suspect that we will need this Send/?Send feature for future bindings as well (Python comes to mind), and so we could introduce a separate feature for this, so it's not tied to wasm exclusively. Maybe non-send-storage, or some better name 😅? Anyway, this might be overdoing it for now. I think we can change this in non-breaking ways in the future.

bindings/wasm/src/account/storage/traits.rs Show resolved Hide resolved
bindings/wasm/src/account/storage/traits.rs Show resolved Hide resolved
Comment on lines 263 to 264
/** Deletes the keypair specified by `location`.*/
keyDel: (did: DID, keyLocation: KeyLocation) => Promise<void>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is keyDel supposed to be idempotent? This came up when I looked over the memory_storage.ts implementation, which behaved slightly differently from the Rust MemStore. Idempotency seems like a good idea to me here, and we should state that in the doc as well, then. That would also require changes to all implementations. Feel free to leave this for a later PR, in which case we should open an issue, so it's not forgotten about. I can open the issue if there is agreement for that.

Comment on lines +284 to +288
/** Returns the last generation that has been published to the tangle for the given `did`.*/
publishedGeneration: (did: DID) => Promise<Generation>;

/** Sets the last generation that has been published to the tangle for the given `did`.*/
setPublishedGeneration: (did: DID, generation: Generation) => Promise<void>;
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume this will be removed in the napi or epic branch?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Handled on #660

Copy link
Contributor

@PhilippGackstatter PhilippGackstatter left a comment

Choose a reason for hiding this comment

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

A few minor notes on the latest commits, will do another pass in a bit.

bindings/wasm/src/account/storage/traits.rs Outdated Show resolved Hide resolved
bindings/wasm/src/account/storage/traits.rs Outdated Show resolved Hide resolved
bindings/wasm/src/core/ed25519.rs Outdated Show resolved Hide resolved
bindings/wasm/src/tangle/message_did.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@PhilippGackstatter PhilippGackstatter left a comment

Choose a reason for hiding this comment

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

This looks good to me now. Thank you very much for addressing all the comments! This is a really solid Rust "trait export" to Wasm, imho.

I honestly may have missed a thing or two because of the split in epic branch and napi stronghold, but since this goes through more reviews in those PRs I believe that should be okay.

Copy link
Contributor

@cycraig cycraig left a comment

Choose a reason for hiding this comment

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

I assume things like DIDLease and publishedGeneration have been removed in the other PR.

Otherwise it seems good overall, few general comments.

Comment on lines +19 to +21
public async setPassword(_encryptionKey: Uint8Array): Promise<void> {}

public async flushChanges(): Promise<void> {}
Copy link
Contributor

Choose a reason for hiding this comment

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

We might need a comment here explaining that these functions are not necessary here but are needed for other Storage implementations. I.e. it's fine to have no-op functions?

Aside: still not sure if we're going to keep setPassword, since I'm hoping that Stronghold will take over password management. Basically authentication should be part of the implementation rather than the interface, ideally.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Good point, it should be public async flushChanges(): Promise<void> {return Promise.resolve()}}

Copy link
Contributor

Choose a reason for hiding this comment

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

Fully agree setPassword should be removed, but perhaps in a follow-up PR? The changes required are minimal, I think, so it could be done here as well. I'm fine either way. Passwords should be passed to storage constructors and not be required post-creation anymore, which will, for example, be the case with the upcoming stronghold refactor. Some storages might not even need a password, so it shouldn't be part of the interface.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm fine with any changes being added to an issue or appended to #669, as long as there's something to keep track of them.

Comment on lines +79 to +84
#[cfg(feature = "wasm")]
#[error("JsValue serialization error: {0}")]
SerializationError(String),
#[cfg(feature = "wasm")]
#[error("javascript function threw an exception: {0}")]
JsError(String),
Copy link
Contributor

Choose a reason for hiding this comment

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

Regarding errors for the Storage interface, I think it's a bit unwieldy to restrict implementers to the top-level AccountError.

Maybe we can have a sub-error enum for the Storage and map them to a single AccountError internally? This would be similar to the Error in the diff crate, where each variant maps to a function.

#[derive(Debug, thiserror::Error, strum::IntoStaticStr)]
pub enum Error {
  #[error("Diff Error: {0}")]
  DiffError(String),
  #[error("Merge Error: {0}")]
  MergeError(String),
  #[error("Conversion Error: {0}")]
  ConversionError(String),
}

We could use strings too or retain the error source with e.g. Box<dyn Error> to preserve reporting, as mentioned in #636.

This would basically eliminate the need for these feature-gated JsError/SerializationError variants since we could define errors in the Wasm bindings themselves that are bubbled up to the correct variant with no loss of context (hopefully).

This would benefit future bindings and Storage implementers using the core Rust library too.

cc @olivereanderson

Copy link
Contributor

Choose a reason for hiding this comment

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

There definitely needs to be a separate StorageError, and I really like your suggestion with Box<dyn Error>. Still, I think this needs a good amount of thought put into it to provide a reasonably abstract error that can accommodate not just stronghold and memory stores, which is why I would suggest doing this in a separate PR and leave the current approach as an acceptable temporary solution. I'd be happy to open an issue for it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure, it can be added to #669 or its own issue. I wouldn't merge this without having a tracking issue for what's left, however.

Comment on lines +125 to +126
let signature: Uint8Array = Ed25519.sign(data, keyPair.private);
return new Signature(keyPair.public, signature)
Copy link
Contributor

Choose a reason for hiding this comment

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

This discussion is continued in the related issue #669 (comment)

Comment on lines +22 to +37
#[wasm_bindgen(js_name = "fromBase58String")]
pub fn from_base58_string(private_key: &str) -> Result<WasmPrivateKey> {
let private_key: PrivateKey = decode_b58(private_key).map_err(wasm_error)?.into();
let private_key_bytes: [u8; 32] = <[u8; 32]>::try_from(private_key.as_ref())
.map_err(|err| AccountError::InvalidPrivateKey(format!("expected a slice of 32 bytes - {}", err)))
.wasm_result()?;
Ok(WasmPrivateKey(ed25519::SecretKey::from_bytes(private_key_bytes)))
}

/// Returns a base58 encoded string that represents the PublicKey.
#[wasm_bindgen(js_name = publicKey)]
pub fn public_key(&self) -> String {
let public: ed25519::PublicKey = self.0.public_key();
let public_key: PublicKey = public.to_bytes().to_vec().into();
encode_b58(&public_key)
}
Copy link
Contributor

@cycraig cycraig Feb 22, 2022

Choose a reason for hiding this comment

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

Mentioned in #669 that we should probably move to UInt8Array rather than Base58-BTC encoded strings but that's a larger refactor for the Wasm bindings. We should really use the same types everywhere though, so WasmPrivateKey in WasmDocument and WasmKeyPair too?

Doesn't need to be addressed in this PR, maybe added to #669 as a discussion point?

Comment on lines +136 to +137
/// Convenience struct to convert Result<JsValue, JsValue> to an AccountResult<_, AccountError>
pub struct JsValueResult(pub(crate) Result<JsValue>);
Copy link
Contributor

Choose a reason for hiding this comment

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

See the previous comment on possibly refactoring Storage errors, it might make JsValueResult unnecessary since errors would be mapped to e.g. StorageError variants internally?

Comment on lines +7 to 10
#[derive(Clone, Debug, Deserialize)]
pub struct Signature {
pub(crate) pkey: PublicKey,
pub(crate) data: Vec<u8>,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I commented about this on the other PR but do we need Signature to contain the PublicKey as well? I don't think it's used. Is it needed for future signature schemes?

Not related to the changes in this PR but it is about the Storage implementation.

Copy link
Contributor

Choose a reason for hiding this comment

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

No we don't need it. In fact, this is unused in the account, too. The account Signature type could need a refactoring to simply return either a Vec<u8> or a newtype wrapper around it. The pkey is simply discarded in RemoteSign. Could be part of a number of storage refactors mentioned before (redesign errors, remove set_password, refactor Signature)?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm fine with any changes being added to an issue or appended to #669.

@HenriqueNogara HenriqueNogara merged commit 4b0164c into epic/wasm-bindings-account Feb 22, 2022
@cycraig cycraig mentioned this pull request Feb 22, 2022
20 tasks
@HenriqueNogara HenriqueNogara deleted the feat/wasm-storage branch March 3, 2022 12:41
@cycraig cycraig mentioned this pull request Mar 14, 2022
10 tasks
@cycraig cycraig changed the title Feat/wasm storage Add Wasm Account Storage interface Mar 17, 2022
@cycraig cycraig removed the Enhancement New feature or improvement to an existing feature label Mar 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Added A new feature that requires a minor release. Part of "Added" section in changelog Wasm Related to Wasm bindings. Becomes part of the Wasm changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants