Using the Oracle on Solana

This article explains how the Umbrella Network's Chain Solana program works. It is a guide for developers who want to verify Layer-2 data in Solana. We provide scripts as examples in our Reference Application.

Account structure

On Solana, we dynamically create an account for each block where we store the Merkel root hash for L2 data verification.

All the accounts in Solana have an address (public key). In our case, we make use of Partial Derived Addresses (PDA) which are public keys that don't rely on the elliptic curve, and as a consequence, they don't have any associated private key. We use this PDA for its hash-map property.

How to get data from an account

The first step is to compute the PDA for the account we are interested in. This is done with the findProgramAddress method which takes the ProgramId and a seed as parameters.

For Umbrella Network's Oracle, the seed is the block ID:

const programId = "4SPgs3L7Ey9VyRuZwx4X3y86LSAZXP2Hhpz9Sps4v3iT";
const seed = LeafValueCoder.encode(blockId, '');
const [blockPda, bump] = await PublicKey.findProgramAddress(
    [seed], program.programId
);

๐Ÿ“˜

The LeafValueCoder, an object belonging to Umbrella Network;s SDK, serves to encode and decode common data types such as numbers or strings into data buffers which are most useful for being processed on the blockchain.

๐Ÿ“˜

PublicKey is an object that belongs to the @solana/web3.js interface and contains the findProgramAddress function, This function converts the seed and the program ID into a PDA.
These two parameters uniquely determine the blockPda.

After obtaining the blockPDA, we can proceed fetching the data using the program object:

let block = await program.account.authority.fetch(blockPda);

Verifying Layer-2 Data

In order to verify the L2D, we will need first to retrieve the PDA for the account storing the information of the specific block the L2D belongs to. This is done by using the block ID as seed for the `FindProgramAddress' function.

Follows an example for retrieving account for block id: 505332

const programId = "4SPgs3L7Ey9VyRuZwx4X3y86LSAZXP2Hhpz9Sps4v3iT";
let block_id = 505332;
const seed = LeafValueCoder.encode(block_id, '');
const [blockPda, bump] = await PublicKey.findProgramAddress(
    [seed], program.programId
);
let block = await program.account.authority.fetch(blockPda);

For actually verifiying the proofs for a L2D we need to invoke the verify_proof_for_block function in the Chain program. This function returns TRUE if the proofs were verified (meaning the value for the indicated key is teh one validated by the Oracle for provided block id) or FALSE if not,

In code, this can be performed as:

await program.methods
  .verifyProofForBlock(seed, proofs, key, value)
  .accounts({
    verifyResult: verifyResultAccount.publicKey,
    block: blockPda,
  })
  .rpc({ commitment: 'confirmed' });

// we check the result
const account = await program.account.verifyResult.fetch(verifyResultAccount.publicKey);
console.log('Verify result =', account.result);

where:

  • seed: is the block id encoded as bytes
  • key: is the key of the L2D to verify (e.g. "BTC-USD") encoded as bytes
  • value: is the value (associated to the key) encoded as bytes
  • proofs: is an array of the proofs for that particular key-value pair

The only way Solana programs can return on-chain information to the real world is also through the use of accounts, which means that the result of the verify_proof_for_block should be written on an account. For this purpose, the VerifyResult account must be created.

The following code generates a VerifyAccount account which will be use to store the result of the L2D verification:

const verifyResultAccount = anchor.web3.Keypair.generate();

await program.methods
  .initializeVerifyResult()
  .accounts({
    verifyResult: verifyResultAccount.publicKey,
    user: provider.wallet.publicKey,
    systemProgram: SystemProgram.programId,
  })
  .signers([verifyResultAccount])
  .rpc({ commitment: 'confirmed' });

where:

  • verifyResult: is the address (public key) that the account will have (in this case it is a random generated address)
  • user: is the address (public key) of the account that will provide the funds for creating this account
  • systemProgram: is the PDA of a Solana program used to initialize accounts

The next figure illustrates the process described above:

1430

Block validation through cross-program invocation (CPI)

In a similar way, it is also possible to verify data through another program on Solana. This second program could be for example a DEX or any other dApp that wants to confirm a price retrieved for a L2D is correct. For this purpose, the caller program will perform a cross-program invocation to our chain program. In turn, the latter will write the result also on the VerifyResult account or return it directly to the caller program for internal use.

1430