Architecture

Kora is a Rust workspace with three crates:

  • kora-lib holds the core logic and the RPC server,
  • kora-cli builds the kora binary operators run, and
  • kora-deploy is a specialized program deployment tool, versioned separately. Everything below lives in crates/lib/src unless noted.

The request lifecycle

The diagram below identifies the general flow of a transaction through Kora. Understanding the call path is important to configuring a Kora node and adding new features.

A transaction travels from the client app through the gateway, validation, plugins, and signing, before Kora either returns it or broadcasts it. Each stage is governed by a matching section of kora.toml or signers.toml.

Where each stage lives

Stage in the diagramSourceEntry point
Gatewayrpc_server/server.rsrun_rpc_server
Dispatchrpc_server/rpc.rsKoraRpc
Per-method logicrpc_server/method/one file per JSON-RPC method
Validationvalidator/transaction_validator.rsTransactionValidator
Pluginsplugin/mod.rsTransactionPluginRunner
Signingsigner/backed by the solana-keychain crate

Two more files to know: config.rs defines the entire kora.toml schema, and state.rs holds the deserialized config in process-global state.

Reading the RPC server

rpc.rs is a thin dispatch layer, one method per JSON-RPC endpoint, and the fastest way to see the whole API. Each method's logic sits in its own file under rpc_server/method/. Read sign_transaction.rs end to end; the other signing methods are variations on it.

Reading the validator

transaction_validator.rs and transaction/instruction_util.rs hold logic for validating transactions and instructions. Additionally, the fee-payer-policy safety property tests, which live one file per gated program under validator/transaction_validator/fee_payer_policy_props/.

Conventions worth knowing

Default validation. Every flag under [validation.fee_payer_policy] defaults to false, and the policy structs carry #[serde(default)]. That pairing is deliberate: adding a newly gated instruction must not break an existing operator's config, and it must land as a denial rather than an accidental allowance. Preserve both when you add a flag.

Validate the resolved transaction, never the raw one. The raw VersionedTransaction exposes only static account keys, so validating or pricing a V0 transaction from it silently skips lookup-table accounts and under-charges. VersionedTransactionResolved::from_transaction resolves those addresses up front, which is why every downstream stage takes the resolved type.

Adding a transaction plugin

Plugins implement the TransactionPlugin trait in plugin/mod.rs. It has two methods: validate runs per transaction during the signing flows, and validate_config runs once at startup and returns errors and warnings for the operator. A PluginExecutionContext tells the plugin which of the four signing methods invoked it.

TransactionPluginRunner::from_config matches on TransactionPluginType, defined in config.rs. A new built-in plugin is a new variant there plus a new match arm in the runner. The two plugins that ship today are gas_swap and deploy_authority.

Testing

Kora keeps configuration in process-global state, so tests/ declares one [[test]] target per phase, and each target boots its own embedded surfnet plus Kora node, seeded from scratch. One config per binary: a new phase needs a new target, not a new module in an existing one.

cargo test -p tests --test <target>

Everything else is wired through the justfile. Read that rather than reconstructing commands by hand.

Is this page helpful?

Содержание

Редактировать страницу
Architecture | Solana