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

Allow storage of #[state] values with alternate encodings #550

Closed
preston-evans98 opened this issue Jul 24, 2023 · 2 comments · Fixed by #648
Closed

Allow storage of #[state] values with alternate encodings #550

preston-evans98 opened this issue Jul 24, 2023 · 2 comments · Fixed by #648

Comments

@preston-evans98
Copy link
Member

Background

The jmt crate implements a byte-oriented Key/Value store. The Sovereign SDK transforms that byte store into a typed key/value store using the StateValue and StateMap abstractions. When a #[state] value is set, the underlying implementation uses the StorageKey::new() and StorageValue::new() constructors to serialize the key and value with borsh before inserting them into the current WorkingSet.

While this system works very well for keys and values which implement borsh, it has a few limitations:

  1. Items which don't implement the borsh serialize/deserialize traits can't be stored, so many foreign types have to be wrapped in a newtype
  2. It adds an additional layer of serialization when storing pre-encoded types
  3. It prevents us from supporting IBC, since IBC requires that the chain's authenticated state tree must store the raw protobuf encodings of certain messages.

Desired Outcome

To support IBC, we need to be able to take the Rust struct representing an IBC message and store its protobuf encoding into the state tree without introducing any additional serialization. There are at least two designs which will support this pattern.

Option 1: Add a (phantom) serializer to the StateValue

In this option, we change the API of StateValue from StateValue<T> to StateValue<T, C>. A rough sketch of the APIs is as follows:

pub struct StateValue<V, C> { 
    _phantom: PhantomData<(V, C)>,
    prefix: Prefix,
}

impl<T, C: SomeCodecTrait> StateValue<T, C> { 
  fn set(&self, value: &T, working_set: WorkingSet<...>) { 
    let value = StorageValue::new::<C>::(value);
    working_set.set(self.prefix(), value)
  } 
} 

impl StorageValue { 
  fn new<C: SomeCodecTrait>(value: &C) -> Self { 
     let encoded_value = C::encode(value);
        Self {
            value: Arc::new(encoded_value),
        }
  }
} 

Note that this description is slightly inaccurate, since it pretends that the WorkingSet accepts a StorageValue as an argument. In reality, the WorkingSet accepts the raw type and calls the StorageValue constructor internally - so we would need to add generics to the WorkingSet::set function as well.

Option 2: Add a new RawStateValue type; add a from_bytes implementation to StorageKey and StorageValue.

pub struct RawStateValue { 
    prefix: Prefix,
}

impl<T, C: SomeCodecTrait> RawStateValue<T, C> { 
  fn set(&self, value: Vec<u8> working_set: &mut WorkingSet<...>) { 
    let value = StorageValue::from_bytes(value);
    working_set.set(self.prefix(), value)
  } 
} 

impl StorageValue { 
  fn from_bytes(value: Vec<u8>) -> Self { 
        Self {
            value: Arc::new(value),
        }
  }
} 

@bkolad
Copy link
Member

bkolad commented Jul 24, 2023

We could slightly modify option 1 to use default generics instead of SomeCodecTrait:

pub struct StateValue<V, S = BorshSerializer> {
    _phantom: PhantomData<(V, S)>,
    prefix: Prefix,
}

impl <V>StateValue<V> {               <-- defaults to BorshSerializer
    fn set(&self, value: &T, working_set: WorkingSet<...>) { 
        ...
    }

    fn get(..)..{

    }
}

impl <V>StateValue <V, ProtoSerializer>{
    fn set(&self, value: &T, working_set: WorkingSet<...>) { 
        ...
    }

    fn get(..)..{
        
    }
}

This way:

  1. StateValue without a second generic will default to Borsh (our current code will compile without modification).
  2. We can have identical method signatures (set/get) for BorshSerializer & ProtoSerializer without hitting the specialization issues.

@preston-evans98
Copy link
Member Author

That sounds like a great idea, thanks @bkolad!

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 a pull request may close this issue.

2 participants