Skip to content

Conversation

@drozdziak1
Copy link
Contributor

This change replaces the big attestation state PDA with individual per-symbol ones. This ensures more predictable tx costs and is generally cheaper. The change is achieved by reusing the product accounts on the attest() endpoint and putting the state PDAs in their place.

This change bumps price batch format to v3.1 with a new backwards
compatible field - prev_attestation_time. This is the last time we've
successfully attested the price. If no prior record exists, the
current time is used (the same as attestation_time).

The new field is backed by a new PDA for the attester contract, called
'attestation state'. In this PDA, we store a Pubkey -> Metadata
hashmap for every price. Currently, the metadata stores just the
latest successful attestation timestamp for use with the new field.
@vercel
Copy link

vercel bot commented Feb 6, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

2 Ignored Deployments
Name Status Preview Comments Updated
example-oracle-amm ⬜️ Ignored (Inspect) Visit Preview Feb 8, 2023 at 3:35PM (UTC)
xc-admin-frontend ⬜️ Ignored (Inspect) Visit Preview Feb 8, 2023 at 3:35PM (UTC)

Using Option<> for this makes fallback to latest value more convenient
@drozdziak1 drozdziak1 changed the title [WIP]: wormhole_attester: per-symbol PDAs for attestation state wormhole_attester: per-symbol PDAs for attestation state Feb 6, 2023
Comment on lines 72 to 86
pub pyth_state: AttestationStatePDA<'b>,
pub pyth_price: Info<'b>,

pub pyth_product2: Option<Info<'b>>,
pub pyth_price2: Option<Info<'b>>,
pub pyth_state2: Option<AttestationStatePDA<'b>>,
pub pyth_price2: Option<Info<'b>>,

pub pyth_product3: Option<Info<'b>>,
pub pyth_price3: Option<Info<'b>>,
pub pyth_state3: Option<AttestationStatePDA<'b>>,
pub pyth_price3: Option<Info<'b>>,

pub pyth_product4: Option<Info<'b>>,
pub pyth_price4: Option<Info<'b>>,
pub pyth_state4: Option<AttestationStatePDA<'b>>,
pub pyth_price4: Option<Info<'b>>,

pub pyth_product5: Option<Info<'b>>,
pub pyth_price5: Option<Info<'b>>,
pub pyth_state5: Option<AttestationStatePDA<'b>>,
pub pyth_price5: Option<Info<'b>>,

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess you need to make the states mutable here

Copy link
Collaborator

@ali-behjati ali-behjati left a comment

Choose a reason for hiding this comment

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

lgtm, i'll approve once the CI works. It seems like a mutability issue that I think is related to considering the state accounts mutable (on the contract)

state.0.info(),
accs.payer.key,
solitaire::CreationLamports::Exempt,
state_serialized.len(),
Copy link
Contributor

@guibescos guibescos Feb 6, 2023

Choose a reason for hiding this comment

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

Instead of using state_serialized.len(), you can create a constant AttestationState::LEN which is equal to the maximum size the account can have. Then you can deleting all the resizing code.

Copy link
Contributor

@guibescos guibescos Feb 6, 2023

Choose a reason for hiding this comment

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

Or turn last_attested_trading_publish_time into a Timestamp instead of an Option (the comment below) and use the AccountSize solitaire trait.

Copy link
Contributor Author

@drozdziak1 drozdziak1 Feb 8, 2023

Choose a reason for hiding this comment

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

I think I prefer spending the extra gas in exchange for not maintaining a constant for this. I expect us to want more metadata in this state at some point, if we don't drop the attester in the future. I think without the realloc logic it can live in the uninitialized if branch, so this is only done once in a PDA's lifetime ( that's the case in the change that removes the unneeded realloc()). Fundamentally, I think Solitaire could benefit from a flag that triggers this call for uninitialized data accounts. On persist() it serializes the struct anyway.

pub struct AttestationState {
/// The last trading publish_time this attester saw
pub last_attested_trading_publish_time: UnixTimestamp,
pub last_attested_trading_publish_time: Option<UnixTimestamp>,
Copy link
Contributor

@guibescos guibescos Feb 6, 2023

Choose a reason for hiding this comment

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

I don't think you need this to be an option.
Because the option logic is already captured in whether the account is initialized or not.
i.e. if the account doesn't exist it's a none
if it exists it's a some

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That makes sense. Changed to just look at is_initialized

Data<'b, AttestationStateMap, { AccountState::MaybeInitialized }>,
"p2w-attestation-state-v1",
>;
impl<'a, 'b: 'a> Peel<'a, 'b> for AttestationStatePDA<'b> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Surprised you have to derive peel and persist manually. Why can't solitaire handle this?

Copy link
Contributor Author

@drozdziak1 drozdziak1 Feb 7, 2023

Choose a reason for hiding this comment

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

tl;dr that's because automating this in a derive macro is surprisingly difficult and Solitaire cannot do this currently

Going on a bit of a framework design tangent, it's actually a very interesting topic. I think a solid dedicated derive(Peel) could make this go away. The thing with Solana account deserialization is that aimost any attempt at a framework will focus on adding behavior modifiers on some sort of user accounts struct, one way or another. Anchor chose to go with macro attributes as the first class citizen, where you just specify requirements in the spirit of ETH modifiers, onto struct members as part of the derive macro. Solitaire went a different direction to do as little simple codegen as necessary. The idea being to contain as much of the validation logic in regular Rust code instead of the derive macro. So the way to enforce specific rules is abstracted away from the derive macro (Notice how it says FromAccounts and not Peel). The macro only knows that all members should implement Peel and it just calls that on each account in the incoming slice on-chain. Thus most automated validation comes from stacking types like Mut<Derive<Data<MyDataStruct, Initialized>>>, because they implement Peel and try to cover most simple cases. There are several unanticipated problems with our approach.First,t Solana's Rust safety choices make it very hard to come up with easy to use generics (we have to use lifetimes like 'a, 'b and so on). Also, our struct-based strategy that makes very little info available to the derive macro means that relationships between account struct members are almost impossible to express statically in struct definition

Copy link
Contributor

Choose a reason for hiding this comment

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

AHHHH ok I think I get what's the problem

Copy link
Contributor

Choose a reason for hiding this comment

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

You can get away with this by making :
pub type AttestationStatePDA<'b> =Data<'b, AttestationState, { AccountState::MaybeInitialized }>
instead of :

pub struct AttestationStatePDA<'b>(
    pub Data<'b, AttestationState, { AccountState::MaybeInitialized }>,
);

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 this is just a strict simplification of the code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's exactly what I tried to do before I needed to add the Seeded impl. However, It is not possible to implement foreign traits for foreign types in Rust :( In this case, the typedef is considered equivalent to the foreign type we assign to it, so the compiler yells, because the trait is also foreign.

Copy link
Contributor

@guibescos guibescos left a comment

Choose a reason for hiding this comment

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

Some comments

Copy link
Collaborator

@ali-behjati ali-behjati left a comment

Choose a reason for hiding this comment

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

🔥

Copy link
Contributor

@jayantk jayantk left a comment

Choose a reason for hiding this comment

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

i'm happy if @ali-bahjati and @guibescos are

// Serialize the state to calculate rent/account size adjustments
let state_serialized = state.0 .0 .1.try_to_vec()?;

if state.0 .0.is_initialized() {
Copy link
Contributor

Choose a reason for hiding this comment

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

extract a helper function for all of this account allocation logic

Copy link
Contributor

@guibescos guibescos Feb 7, 2023

Choose a reason for hiding this comment

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

I think this realloc logic needs to go away. This account is fixed sized. It should never get realloc and this code is too complex.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, this made sense only for the large PDA. Thanks.

@drozdziak1 drozdziak1 merged commit d52f4d4 into main Feb 8, 2023
@drozdziak1 drozdziak1 deleted the drozdziak1/wormhole-attester-prev-timestamp-field branch February 8, 2023 16:04
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