Skip to content

tomlinton/example-helloworld

 
 

Repository files navigation

Build status Gitpod Ready-to-Code

Hello world on Solana

This project demonstrates how to use the Solana Javascript API to build, deploy, and interact with programs on the Solana blockchain.

The project comprises of:

  • An on-chain hello world program
  • A client that can send a "hello" to an account and get back the number of times "hello" has been sent

Table of Contents

Quick Start

Open in Gitpod

If you decide to open in Gitpod then refer to README-gitpod.md, otherwise continue reading.

The following dependencies are required to build and run this example, depending on your OS, they may already be installed:

$ npm --version
$ docker -v
$ wget --version
$ rustc --version

Fetch the npm dependencies, including @solana/web3.js, by running:

$ npm install

Start local Solana cluster

This example connects to a local Solana cluster by default.

Enable on-chain program logs:

$ export RUST_LOG=solana_runtime::system_instruction_processor=trace,solana_bpf_loader=debug,solana_rbpf=debug

Start a local Solana cluster:

$ npm run localnet:update
$ npm run localnet:up

View the cluster logs:

$ npm run localnet:logs

Note: To stop the local Solana cluster later:

$ npm run localnet:down

Build the on-chain program

$ npm run build:program

Run client

$ npm run start

Example expected output

Lets say hello to a Solana account...
Connection to cluster established: http://localhost:8899 { solana-core: 1.1.2 }
Loading hello world program...
Program loaded to account 47bZX1D1tdmw3KWTo5MfBrAwwHBJQQzQL4VnNGT7HtyQ
Creating account Eys1jdLHdZ2AE56QAKpfadbjziMZ6NAvpL7qsdtM6sbk to say hello to
Saying hello to Eys1jdLHdZ2AE56QAKpfadbjziMZ6NAvpL7qsdtM6sbk
Eys1jdLHdZ2AE56QAKpfadbjziMZ6NAvpL7qsdtM6sbk has been greeted 1 times
Success

Not seeing the expected output?

  • Ensure you've started the local cluster and built the on-chain program.
  • Ensure Docker is running. You might try bumping up its resource settings, 8 GB of memory and 3 GB of swap should help.
  • Inspect the Solana cluster logs looking for any failed transactions or failed on-chain programs
    • Expand the log filter and restart the cluster to see more detail
      • $ npm run localnet:down
        $ export RUST_LOG=solana_runtime::native_loader=trace,solana_runtime::system_instruction_processor=trace,solana_runtime::bank=debug,solana_bpf_loader=debug,solana_rbpf=debug
        $ npm run localnet:up
        

Customizing the Program

To customize the example, make changes to the files under /src. If you change any files under /src/program you will need to rebuild the on-chain program

Now when you rerun npm run start, you should see the results of your changes.

Learn about Solana

More information about how Solana works is available in the Solana documentation and all the source code is available on github

Learn about the client

The client in this example is written in JavaScript using:

Entrypoint

The client's entrypoint does four things

Establish a connection to the cluster

The client establishes a connection with the client by calling establishConnection.

Load the helloworld on-chain program if not already loaded

The process of loading a program on the cluster includes storing the shared object's bytes in a Solana account's data vector and marking the account executable.

The client loads the program by calling loadProgram. The first time loadProgram is called the client:

  • Read the shared object from the file system
  • Calculates the fees associated with loading the program
  • Airdrops lamports to a payer account to pay for the load
  • Loads the program via the Solana web3.js function 'BPFLoader.load'
  • Creates a new "greeter" account that will be used in the "Hello" transaction
  • Records the public key of both the loaded helloworld program and the "greeter" account in a config file. Repeated calls to the client will refer to the same loaded program and "greeter" account. (To force the reload of the program issue npm clean:store)

Send a "Hello" transaction to the on-chain program

The client then constructs and sends a "Hello" transaction to the program by calling sayHello. The transaction contains a single very simple instruction that primarily caries the public key of the helloworld program account to call and the "greeter" account to which the client wishes to say "Hello" to.

Query the Solana account used in the "Hello" transaction

Each time the client says "Hello" to an account, the program increments a numerical count in the "greeter" account's data. The client queries the "greeter" account's data to discover the current number of times the account has been greeted by calling reportHellos

Learn about the on-chain program

The on-chain helloworld program is a Rust program compiled to Berkley Packet Format (BPF) and stored as an Executable and Linkable Format (ELF) shared object.

The program is written using:

Entrypoint

The program's entrypoint takes three parameters:

fn process_instruction<'a>(
    program_id: &Pubkey, // Public key of the account the hello world program was loaded into
    accounts: &'a [AccountInfo<'a>], // The account to say hello to
    _instruction_data: &[u8], // Ignored, all helloworld instructions are hellos
) -> ProgramResult {
  • program_id is the public key of the currently executing program. The same program can be uploaded to the cluster under different accounts, and a program can use program_id to determine which instance of the program is currently executing.
  • accounts is a slice of `Account Info's representing each account included in the instruction being processed.
  • _instruction_data is a data vector containing the data passed as part of the instruction. In the case of helloworld no instruction data is passed and thus ignored (all instructions are treated as a "Hello" instruction). Typically the instruction data would contain information about what kind of command the program should process and details about that particular command.

Processing an instruction

Given the inputs to the entrypoint, the result of the instruction are updates to account's lamports and data vectors. In the case of helloworld, the "greeted" account's data holds a 32-bit Little-endian encoded unsigned integer, which gets incremented.

The program does a series of checks to ensure that the instruction is well-formed (the "greeted" account is owned by the program and has sufficient data to hold a 32-bit unsigned integer).

The accounts slice may contain the same account in multiple positions, so a Rust std protects any writable data::cell::RefCell

The program prints a diagnostic message to the validators' logs by calling info!. On a local cluster you can view the logs by including solana_bpf_loader_program=info in RUST_LOG.

If the program fails, it returns a ProgramError; otherwise, it returns Ok(()) to indicate to the runtime that any updates to the accounts may be recorded on the chain.

Rust limitations

On-chain Rust programs support most of Rust's libstd, libcore, and liballoc, as well as many 3rd party crates.

There are some limitations since these programs run in a resource-constrained, single-threaded environment, and must be deterministic:

  • No access to
    • rand or any crates that depend on it
    • std::fs
    • std::net
    • std::os
    • std::future
    • std::net
    • std::process
    • std::sync
    • std::task
    • std::thread
    • std::time
  • Limited access to:
    • std::hash
    • std::os
  • Bincode is extreamly computationally expensive in both cycles and call depth and should be avoided
  • String formating should be avoided since it is also computationaly expensive
  • No support for println!, print!, the Solana SDK helpers in src/log.rs should be used instead
  • The runtime enforces a limit on the number of instructions a program can execute during the processing of one instruction

Pointing to the public Solana cluster

Solana maintains a public development cluster called devnet. To connect to the devnet instead of the local cluster, clear the config file, and set the environment variable LIVE to 1.

$ npm run clean:store
$ export LIVE=1

Expand your skills with advanced examples

There is lots more to learn; The following examples demonstrate more advanced features like custom errors, advanced account handling, suggestions for data serialization, benchmarking, etc..

About

Hello world on Solana

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 66.0%
  • Rust 18.5%
  • Shell 15.5%