Skip to content

Commit

Permalink
fix: various bug fixes to client-side deserialization lesson
Browse files Browse the repository at this point in the history
* awk

* awk

remove "in"

* awk

ambiguous "it"

* awk

* id --> ID

* sha --> SHA

* awk

run on

* singular "derivation"

it's a little awk, but is is correct.  Maybe an alt would be to spell 
out the acronym? E.g., "the derivation of Program Derived Addresses 
is..." 
or
"Deriving PDAs is important because..."
...tl;dr it is the derivation (of x) which is (not are) important.

* run on

* awk

* awk

* awk

* to e is to error

consistent with other lesson code examples - we've been spelling it out 
in full
  • Loading branch information
mixelpixel authored May 20, 2022
1 parent 7de6052 commit f819efd
Showing 1 changed file with 19 additions and 19 deletions.
38 changes: 19 additions & 19 deletions content/deserialize-custom-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,38 +24,38 @@ In the last lesson, we serialized custom instruction data that was subsequently

As the saying goes, everything in Solana is an account. Even programs. Programs are accounts that store code and are marked as executable. This code can be executed by the Solana runtime when instructed to do so.

However, programs themselves are stateless. They cannot modify the data within their account. They can only persist state by storing data in other accounts that can be referenced at some other point in time. Understanding how these accounts are used and how to find them is crucial to client-side Solana development.
Programs themselves, however, are stateless. They cannot modify the data within their account. They can only persist state by storing data in other accounts that can be referenced at some other point in time. Understanding how these accounts are used and how to find them is crucial to client-side Solana development.

### PDA

PDA stands for Program Derived Address. As the name suggests, it refers to an address (public key) derived from a program and some seeds. In a previous lesson, we discussed public/private keys and how they are used on Solana. Unlike a keypair, a PDA *does not* have a corresponding private key. The purpose of a PDA is to create an address that a program can sign for in the same way a user may sign for a transaction with their wallet.
PDA stands for Program Derived Address. As the name suggests, it refers to an address (public key) derived from a program and some seeds. In a previous lesson, we discussed public/private keys and how they are used on Solana. Unlike a keypair, a PDA *does not* have a corresponding private key. The purpose of a PDA is to create an address that a program can sign for in the same way a user may sign for a transaction with their wallet.

When you submit a transaction to a program and expect the program to then update state or store data in some way, that program is using one or more PDAs. This is important to understand when developing client-side for two reasons:

1. When submitting a transaction to a program, the client needs to include all addresses for accounts that will be written to or read from. This means that unlike in more traditional client-server architectures, the client needs to have implementation-specific knowledge about the Solana program. It needs to know which PDA is going to be used to store data so that it can include that address in the transaction.
2. Similarly, when reading data from a program, the client needs to know which account(s) to read from.
1. When submitting a transaction to a program, the client needs to include all addresses for accounts that will be written to or read from. This means that unlike more traditional client-server architectures, the client needs to have implementation-specific knowledge about the Solana program. The client needs to know which PDA is going to be used to store data so that it can include that address in the transaction.
2. Similarly, when reading data from a program, the client needs to know which account(s) to read from.

### Finding PDAs

PDAs are not technically created. Rather, they are *found* or *derived* based on one or more input seeds.
PDAs are not technically created. Rather, they are *found* or *derived* based on one or more input seeds.

Regular Solana keypairs lie on the ed2559 Elliptic Curve. This cryptographic function ensures that every point along the curve has a corresponding point somewhere else on the curve and allows for public/private keys. PDAs are addresses that lie *off* the ed2559 Elliptic curve and therefore cannot be signed for by a private key (since there isn’t one). This ensures that the program is the only valid signer for that address.
Regular Solana keypairs lie on the ed2559 Elliptic Curve. This cryptographic function ensures that every point along the curve has a corresponding point somewhere else on the curve, allowing for public/private keys. PDAs are addresses that lie *off* the ed2559 Elliptic curve and therefore cannot be signed for by a private key (since there isn’t one). This ensures that the program is the only valid signer for that address.

To find a public key that does not lie on the ed2559 curve, the program id and seeds of the developer’s choice (like a string of text) are passed through the function [`findProgramAddress(seeds, programid)`](https://solana-labs.github.io/solana-web3.js/classes/PublicKey.html#findProgramAddress). This function combines the program id, seeds, and a bump seed into a buffer and passes it into a sha256 hash to see whether or not the resulting address is on the curve. If the address is on the curve (~50% chance it is), then the bump seed is decremented by 1 and the address is calculated again. The bump seed starts at 255 and progressively iterates down to `bump = 254`, `bump = 253`, etc. until an address is found with the given seeds and bump that does not lie on the ed2559 curve. The `findProgramAddress` function returns the resulting address and the bump used to kick it off the curve, this way the address can be generated anywhere as long as you have the bump and seeds.
To find a public key that does not lie on the ed2559 curve, the program ID and seeds of the developer’s choice (like a string of text) are passed through the function [`findProgramAddress(seeds, programid)`](https://solana-labs.github.io/solana-web3.js/classes/PublicKey.html#findProgramAddress). This function combines the program ID, seeds, and a bump seed into a buffer and passes it into a SHA256 hash to see whether or not the resulting address is on the curve. If the address is on the curve (~50% chance it is), then the bump seed is decremented by 1 and the address is calculated again. The bump seed starts at 255 and progressively iterates down to `bump = 254`, `bump = 253`, etc. until an address is found with the given seeds and bump that does not lie on the ed2559 curve. The `findProgramAddress` function returns the resulting address and the bump used to kick it off the curve. This way, the address can be generated anywhere as long as you have the bump and seeds.

![Screenshot of ed2559 curve](../assets/ed2559-curve.png)

PDAs are a unique concept and are one of the hardest parts of Solana development to understand. If you don’t get it right away, don’t worry. It’ll make more sense the more you practice.

### Why Does This Matter?

The derivation of PDAs are important because the seeds used to find a PDA are what we use to locate the data. For example, a simple program that only uses a single PDA to store global program state might use a simple seed phrase like “GLOBAL_STATE”. If the client wanted to read data from this PDA, it could derive the address using the program id and this same seed.
The derivation of PDAs is important because the seeds used to find a PDA are what we use to locate the data. For example, a simple program that only uses a single PDA to store global program state might use a simple seed phrase like “GLOBAL_STATE”. If the client wanted to read data from this PDA, it could derive the address using the program ID and this same seed.

```tsx
const [pda, bump] = await findProgramAddress(Buffer.from("GLOBAL_STATE"), programId)
```

In more complex programs that store user-specific data, it’s common to use a user’s public key as the seed. This separates each user’s data into its own PDA and makes it possible for the client to locate each user’s data by finding the address using the program id and the user’s public key.
In more complex programs that store user-specific data, it’s common to use a user’s public key as the seed. This separates each user’s data into its own PDA. The separation makes it possible for the client to locate each user’s data by finding the address using the program ID and the user’s public key.

```tsx
const [pda, bump] = await web3.PublicKey.findProgramAddress(
Expand All @@ -66,7 +66,7 @@ const [pda, bump] = await web3.PublicKey.findProgramAddress(
)
```

And, when there are multiple accounts per user, a program may use one or more additional seeds to create and identify accounts. For example, in a note-taking app there may be one account per note where each PDA is derived with the user’s public key and the note’s title.
Also, when there are multiple accounts per user, a program may use one or more additional seeds to create and identify accounts. For example, in a note-taking app there may be one account per note where each PDA is derived with the user’s public key and the note’s title.

```tsx
const [pda, bump] = await web3.PublicKey.findProgramAddress(
Expand Down Expand Up @@ -97,7 +97,7 @@ The `data` property on an `AccountInfo` object is a buffer. To use it efficientl

Deserializing requires knowledge of the account layout ahead of time. When creating your own programs, you will define how this is done as part of that process. Many programs also have documentation on how to deserialize the account data. Otherwise, if the program code is available you can look at the source and determine the structure that way.

To properly deserialize data from an on-chain program, you will have to create a schema client side that mirrors how the data is stored in the account. For example, the following might be the schema for an account storing metadata about a player in an on-chain game.
To properly deserialize data from an on-chain program, you will have to create a client-side schema mirroring how the data is stored in the account. For example, the following might be the schema for an account storing metadata about a player in an on-chain game.

```tsx
import * as borsh from "@project-serum/borsh";
Expand Down Expand Up @@ -127,21 +127,21 @@ const { playerId, name } = borshAccountSchema.decode(buffer)

Let’s practice this together by continuing to work on the Movie Review app from the last lesson. No worries if you’re just jumping into this lesson - it should be possible to follow either way.

As a refresher, this project uses a Solana program deployed to Devnet to let users review movies. Last lesson, we added functionality to the frontend skeleton that let users submit movie reviews. But the list of reviews is still showing mock data. Let’s fix that by working through fetching the program’s storage accounts and deserializing the data stored there.
As a refresher, this project uses a Solana program deployed on Devnet which lets users review movies. Last lesson, we added functionality to the frontend skeleton letting users submit movie reviews but the list of reviews is still showing mock data. Let’s fix that by fetching the program’s storage accounts and deserializing the data stored there.

![Screenshot of movie review frontend](../assets/movie-reviews-frontend.png)

### 1. Download the starter code

If you didn’t complete the demo from the last lesson or just want to make sure that you didn’t miss anything, you can download the [starter code](https://github.com/Unboxed-Software/solana-movie-frontend/tree/solution-serialize-instruction-data).
If you didn’t complete the demo from the last lesson or just want to make sure that you didn’t miss anything, you can download the [starter code](https://github.com/Unboxed-Software/solana-movie-frontend/tree/solution-serialize-instruction-data).

The project is a fairly simple Next.js application. It includes the `WalletContextProvider` we created in the Wallets lesson, a `Card` component for displaying a movie review, a `MovieList` component that displays reviews in a list, a `Form` component for submitting a new review, and a `Movie.ts` file that contains a class definition for a `Movie` object.

Note that when you run `npm run dev`, the reviews displayed on the page are mocks. We’ll be swapping those out for the real deal.

### 2. Create the buffer layout

Remember that to properly interact with a Solana program, you need to know how its data is structured.
Remember that to properly interact with a Solana program, you need to know how its data is structured.

The Movie Review program creates a separate account for each movie review and stores the following data in the account’s `data`:

Expand Down Expand Up @@ -202,8 +202,8 @@ export class Movie {
try {
const { title, rating, description } = this.borshAccountSchema.decode(buffer)
return new Movie(title, rating, description)
} catch(e) {
console.log('Deserialization error:', e)
} catch(error) {
console.log('Deserialization error:', error)
return null
}
}
Expand Down Expand Up @@ -237,7 +237,7 @@ export const MovieList: FC = () => {
setMovies(movies)
})
}, [])

return (
<div>
{
Expand All @@ -248,7 +248,7 @@ export const MovieList: FC = () => {
}
```

At this point, you should be able to run the app and see the list of movie reviews retrieved from the program!
At this point, you should be able to run the app and see the list of movie reviews retrieved from the program!

Depending on how many reviews have been submitted, this may take a long time to load or may lock up your browser entirely. But don’t worry — next lesson we’ll learn how to page and filter accounts so you can be more surgical with what you load.

Expand All @@ -271,4 +271,4 @@ Now it’s your turn to build something independently. Last lesson, you worked o

If you get really stumped, feel free to check out the solution code [here](https://github.com/Unboxed-Software/solana-student-intros-frontend/tree/solution-deserialize-account-data).

As always, get creative with these challenges and take them beyond the instructions if you want!
As always, get creative with these challenges and take them beyond the instructions if you want!

0 comments on commit f819efd

Please sign in to comment.