Skip to main content

Overview

Firedancer is a from-scratch validator client for Solana, designed to be fast, secure, and independent. This guide covers the code style conventions and development practices used throughout the project.
The code style is defined by the code in src/tango. Most contributors do not use automated code formatting tools.

Getting Started

Firedancer currently only supports Linux and requires at least kernel v4.18 to build.
1

Clone the repository

git clone --recurse-submodules https://github.com/firedancer-io/firedancer.git
cd firedancer
2

Install dependencies

./deps.sh +dev
3

Build and run

make -j run
The make run target runs the fddev dev command, which will:
  • Ensure your system is configured correctly
  • Create a genesis block and keys
  • Start a faucet
  • Start a validator on the local machine
fddev will use sudo to make privileged changes to system configuration where needed. If sudo is not available, you may need to run the command as root.

Code Style Guide

General Conventions

Text Word Wrap

Aspire to word wrap text (comments, not code) at 72 columns for readability. This is the level the publishing industry has used for hundreds of years for making print easily readable with minimal eye strain.

File Extensions

ExtensionFile Type
.cStandalone C translation unit
.hReusable C include file, no symbol defs (header)
.cInclude-once C file, with symbol defs
.sAssembly files
(none)Shell scripts

Include Guards

Header files must use #ifndef include guards, not #pragma once. Given file: src/path/to/file/fd_file_name.h:
#ifndef HEADER_fd_src_path_to_file_fd_file_name_h
#define HEADER_fd_src_path_to_file_fd_file_name_h

...

#endif /* HEADER_fd_path_to_file_fd_file_name_h */

Vertical Alignment

Unlike popular code formatting tools, Firedancer uses vertical alignment for better readability.
Good:
#define FD_FOO_SUCCESS    (0)
#define FD_FOO_ERR_PROTO  (1)
#define FD_FOO_ERR_IO    (20)

void
foo( void ) {
  char const * _init = fd_env_strip_cmdline_cstr( &argc, &argv, "--init", NULL, NULL                 );
  uint         seed  = fd_env_strip_cmdline_uint( &argc, &argv, "--seed", NULL, (uint)fd_tickcount() );
  int          lazy  = fd_env_strip_cmdline_int ( &argc, &argv, "--lazy", NULL, 7                    );
}
Wrong:
#define FD_FOO_SUCCESS (0)
#define FD_FOO_ERR_PROTO (1)
#define FD_FOO_ERR_IO (20)

void
foo( void ) {
  char const * _init = fd_env_strip_cmdline_cstr( &argc, &argv, "--init", NULL, NULL );
  uint seed = fd_env_strip_cmdline_uint( &argc, &argv, "--seed", NULL, (uint)fd_tickcount() );
  int lazy = fd_env_strip_cmdline_int( &argc, &argv, "--lazy", NULL, 7 );
}

Spacing Rules

Function Calls

No spaces for function calls with zero arguments:
abort();   /* good */
abort( );  /* WRONG! */
For function calls with arguments, spaces inside brackets, no space before brackets:
printf( "Hello %s\n", "World" );  /* good */
printf("Hello");                  /* WRONG! */
Exception: No spaces with sizeof:
memcpy( dst, src, sizeof(fd_rng_t) );  /* good */
Exception: No spaces between double bracket macros:
FD_LOG_NOTICE(( "pass" ));  /* good */

Control Flow

Annotate uncommon error paths with FD_UNLIKELY. For single-line if statements, no braces required:
if( FD_UNLIKELY( do_crash ) ) abort();
If a branch goes on a separate line, braces are mandatory:
/* good */
if( FD_UNLIKELY( status!=3 ) ) {
  FD_LOG_CRIT(( "Critical error, aborting" ));
}

/* WRONG! */
if( FD_UNLIKELY( status!=3 ) )
  FD_LOG_CRIT(( "Critical error, aborting" ));

Function Prototypes

  • Modifiers and return types on separate lines
  • One function argument per line
  • Vertically align function argument types and names
/* good */
static inline uint
fd_rng_seq_set( fd_rng_t * rng,
                uint       seq );

/* WRONG! */
static inline uint fd_rng_seq_set( fd_rng_t * rng, uint seq );

Type System

Integers

Use fd_util_base.h types instead of stdint.h integer types:
stdintfd_util_base
int8_tschar
uint8_tuchar
int16_tshort
uint16_tushort
int32_tint
uint32_tuint
int64_tlong
ptrdiff_tlong
uint64_tulong
size_tulong

Booleans

Do not use bool (stdbool). Instead use int. The value 1 is “true” and the value 0 is “false”.
int is_working = 1;
if( is_working ) { ... }

Function Documentation

  • Documentation for a function is typically before the function prototype in a comment block
  • Comments should mention the name of the function toward the beginning
  • Function declarations belonging to a public API must be documented
  • Implementations of these functions must not repeat the comment
/* fd_rng_seq_set sets the sequence to be used by rng and returns
   the replaced value. fd_rng_idx_set sets the next slot that will be
   consumed next by rng and returns the replaced value. */

static inline uint
fd_rng_seq_set( fd_rng_t * rng,
                uint       seq );

Macros

These are recommendations. Depending on macro scope, these rules might not make sense.
Enclose arguments in braces:
#define wwl_abs(x) _mm512_abs_epi64( (x) )  /* good */
#define wwl_abs(x) _mm512_abs_epi64( x )    /* WRONG! */
Enclose macro bodies in do/while(0) scopes:
/* good */
#define FD_R43X6_SQR2_INL( za,xa, zb,xb ) \
  do {                                    \
    (za) = fd_r43x6_sqr( (xa) );          \
    (zb) = fd_r43x6_sqr( (xb) );          \
  } while(0)
Only evaluate macro arguments once:
/* good */
#define TRAP(x)               \
  do {                        \
    int _cnt = (x);           \
    if( _cnt<0 ) return _cnt; \
    cnt += _cnt;              \
  } while(0)

/* WRONG! - evaluates (x) multiple times */
#define TRAP(x)             \
  do {                      \
    if( (x)<0 ) return (x); \
    cnt += (x);             \
  } while(0)

Portability

Build Capabilities

Firedancer aspires to compile under any LP64 environment. If any component has more assumptions (e.g. needs a POSIX-like target), it should check for these capabilities via the FD_HAS_{...} switches. Example Makefile:
ifdef FD_HAS_HOSTED
$(call add-objs,fd_numa,fd_util)
endif
Example C code:
#if FD_HAS_HOSTED

...

#endif /* FD_HAS_HOSTED */

Language Features

Try to stick to ISO C17. GNU C extensions are permitted as long as they are well supported by Clang and CBMC many years back.

Compiler Compatibility

As of 2024-Jul, Firedancer builds on GNU/Linux sysroots with:
  • GCC 8.5 or newer
  • Clang
  • CBMC
The “Frankendancer” build target (fdctl) only targets x86_64 with a Haswell-like minimum feature set (AVX2, FMA). Experimental support exists for:
  • musl Linux, macOS, FreeBSD, Solana (SVM) C programs
  • arm64, ppc64le, sBPFv1, sBPFv2

seccomp Sandbox

Firedancer uses a strict sandbox architecture on Linux platforms using seccomp. Be mindful of what syscalls glibc could use under the hood when using standard library APIs.
During initialization, a seccomp profile is installed to each tile containing rules for allowed syscalls. If a syscall is triggered unexpectedly, seccomp will crash Firedancer.

File I/O

Prefer fd_io over stdio.h for streaming file I/O. Make sure to handle EINTR correctly.

Security Best Practices

Fuzzing

Most code should be covered by fuzz tests. Try to:
  • Use graceful error handling instead of aborting/crashing/exiting
  • Provide test APIs for mocking state (e.g. encryption keys when fuzzing a network protocol)

Test Vectors

Firedancer runs a large set of test vectors in CI that tests conformance between Firedancer and Agave’s block, transaction, instruction, and VM execution down to the same error code.
1

Create test fixtures

Make a pull request into the test-vectors repository with your new fixtures. See solana-conformance for more information on generating fixtures.
2

Update Firedancer

Once the pull request is merged, make a pull request into Firedancer, updating contrib/test/test-vectors-fixtures/test-vectors-commit-sha.txt with the latest test vectors GitHub commit SHA.

Complex Function Exit

Sometimes complex control flow is unavoidable. A typical error is failure to release resources on variables that go out of scope. Instead of multiple return points, use a do/while scope:
do {
  if( fail1 ) break;
  ...
  if( fail2 ) break;
  ...
} while(0);

cleanup();
return;
In egregious cases, you may use the cleanup attribute to execute an inline function when a variable goes out of scope. See FD_SCRATCH_SCOPE_BEGIN in src/util/scratch/fd_scratch.h for examples.

Resources

Build docs developers (and LLMs) love