hyperledger-iroha / iroha

Iroha - A simple, enterprise-grade decentralized ledger
https://wiki.hyperledger.org/display/iroha
Apache License 2.0
438 stars 280 forks source link

Lazy deserialization for PublicKey inside WASM #5038

Closed dima74 closed 1 month ago

dima74 commented 1 month ago

In case of single peer tps performance, >60% of time takes WASM call for transaction validation, and most of time inside WASM takes PublicKey::decode/PublicKey::from_bytes (https://github.com/hyperledger/iroha/issues/4727#issuecomment-2329677940). However inside WASM public key is used mostly for comparison (==). Comparision can be performed without deserialization, so I propose to make deserialization lazy inside WASM. In particular adding wrapper like this for PublicKeyInner:

struct PublicKeyInnerLazy {
    algorithm: Algorithm,
    payload: Vec<u8>,
    key: OnceCell<PublicKeyInner>,
}

Background: why PublicKey::from_bytes is slow. Consider call of PublicKey::from_bytes with Algorithm::Ed25519 and 32 bytes payload. The result of this call will be ed25519_dalek::VerifyingKey:

struct VerifyingKey {
    // 32 bytes of original payload, unchanged
    compressed: CompressedEdwardsY,

    // 160 bytes, decompressed
    point: EdwardsPoint,
}

VerifyingKey::point is obtained from VerifyingKey::compressed using operations like square, pow2k from curve25519 crate.


cc @mversic @Erigara

Erigara commented 1 month ago

Does it mean that data required for key would be doubled?

Also why not use enum to represent key state?

struct PublicKeyLazyInner {
    algorithm: Algorithm,
    state: PublicKeyLazyInnerState,
}

enum PulicKeyLazyInnerState {
    Uninit(Vector<u8>),
    Init(KeyInner),
}

Would require some interior mutability to initialize in otherwise immutable methods.

dima74 commented 1 month ago

Does it mean that data required for key would be doubled?

Also why not use enum to represent key state?

My original proposal is optimized for the assumption that PublicKey inside WASM in almost all cases is used only for comparisons. So if we add Box:

struct PublicKeyInnerLazy {
    algorithm: Algorithm,
    payload: Vec<u8>,
    key: OnceCell<Box<PublicKeyInner>>,
}

then in case PublicKeyInner is not needed, we will have even less memory consumption then now (unitialized OnceCell gives 8 bytes overhead). Do you know how often we need actual PublicKeyInner (that is some signing functionaligy) inside WASM?

Erigara commented 1 month ago

Do you know how often we need actual PublicKeyInner (that is some signing functionaligy) inside WASM?

I'm not aware of such cases actually.