Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,18 @@ Reference configs:
- [docker/conf/default.conf](docker/conf/default.conf) - Minimal shard override
- [Consensus Configuration Guide](https://github.com/F1R3FLY-io/system-integration/blob/main/docs/consensus-configuration.md) - FTT, synchrony threshold semantics, finalization formula, recommended values

### Native Token

Each chain's native token identity is configured before genesis and baked into the on-chain `TokenMetadata` contract. These values are **immutable after genesis**.

| Config Field | CLI Flag | Default |
|---|---|---|
| `native-token-name` | `--native-token-name` | `F1R3CAP` |
| `native-token-symbol` | `--native-token-symbol` | `F1R3` |
| `native-token-decimals` | `--native-token-decimals` | `8` |

After genesis, queryable via `/api/status` or on-chain at `rho:system:tokenMetadata`. Joiners verify their config matches the on-chain values at startup.

## Documentation

Detailed architecture and API documentation for each crate is available in [docs/](docs/README.md):
Expand Down
1 change: 1 addition & 0 deletions casper/src/main/resources/Registry.rho
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ in {
`rho:system:authKey` : `rho:id:1qw5ehmq1x49dey4eadr1h4ncm361w3536asho7dr38iyookwcsp6i`,
`rho:system:makeMint` : `rho:id:asysrwfgzf8bf7sxkiowp4b3tcsy4f8ombi3w96ysox4u3qdmn1wbc`,
`rho:system:pos` : `rho:id:m3xk7h8r54dtqtwsrnxqzhe81baswey66nzw6m533nyd45ptyoybqr`,
`rho:system:tokenMetadata` : `rho:id:jutz139igo7x5gpby3ri1dhwcwnbdghd6onuys3wfnqhhkwffxbfsn`,
`rho:vault:system` : `rho:id:6zcfqnwnaqcwpeyuysx1rm48ndr6sgsbbgjuwf45i5nor3io7dr76j`,
`rho:vault:multiSig` : `rho:id:b9s6j3xeobgset4ndn64hje64grfcj7a43eekb3fh43yso5ujiecfn`
} {
Expand Down
49 changes: 49 additions & 0 deletions casper/src/main/resources/TokenMetadata.rhox
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
The table below describes the required computations and their dependencies

No. | Dependency | Computation method | Result
----+------------+--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------
1. | | given | sk = 8f9a1c3b2d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a
2. | | given | timestamp = 1737500000000
3. | | lastNonce | nonce = 9223372036854775807
4. | 1, | secp256k1 | pk = 04856ecb339aa2462bc25f4d4d5f5e5317c58e366222bed85f488c576f423ff7136ba22c4198b1b7b8d5d2c61722b948fee9d8d8e10e468dc52b490d3393444fd0
5. | 2, 4, 3, | registry | value = (1737500000000, 04856ecb339aa2462bc25f4d4d5f5e5317c58e366222bed85f488c576f423ff7136ba22c4198b1b7b8d5d2c61722b948fee9d8d8e10e468dc52b490d3393444fd0, 9223372036854775807)
6. | 5, | protobuf | toSign = 2a65aa01620a092a071080fc8fb191650a462a44ca014104856ecb339aa2462bc25f4d4d5f5e5317c58e366222bed85f488c576f423ff7136ba22c4198b1b7b8d5d2c61722b948fee9d8d8e10e468dc52b490d3393444fd00a0d2a0b10feffffffffffffffff01
7. | 6, 1, | secp256k1 | sig = 3044022065f630061a79b4ae846a705faf6c96dc3f97d90bb9f1dd1dec8d8581d27f50d902207d99a932eee9a9743bef7c7148db938c4ec34bdd4d99b1445db98bc0a61ec851
8. | 4, | registry | uri = rho:id:jutz139igo7x5gpby3ri1dhwcwnbdghd6onuys3wfnqhhkwffxbfsn
----+------------+--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------
*/
new
TokenMetadata,
rs(`rho:registry:insertSigned:secp256k1`),
uriOut
in {
// Returns the full name of the native token (e.g. "F1R3CAP").
contract TokenMetadata(@"name", ret) = {
ret!("$$nativeTokenName$$")
} |

// Returns the native token ticker symbol (e.g. "F1R3").
contract TokenMetadata(@"symbol", ret) = {
ret!("$$nativeTokenSymbol$$")
} |

// Returns the number of decimal places used to display the token
// (e.g. 8 means 1 token = 10^8 dust).
contract TokenMetadata(@"decimals", ret) = {
ret!($$nativeTokenDecimals$$)
} |

// Returns all token metadata as a tuple (name, symbol, decimals) for
// callers that want a single round-trip.
contract TokenMetadata(@"all", ret) = {
ret!(("$$nativeTokenName$$", "$$nativeTokenSymbol$$", $$nativeTokenDecimals$$))
} |

rs!(
"04856ecb339aa2462bc25f4d4d5f5e5317c58e366222bed85f488c576f423ff7136ba22c4198b1b7b8d5d2c61722b948fee9d8d8e10e468dc52b490d3393444fd0".hexToBytes(),
(9223372036854775807, bundle+{*TokenMetadata}),
"3044022065f630061a79b4ae846a705faf6c96dc3f97d90bb9f1dd1dec8d8581d27f50d902207d99a932eee9a9743bef7c7148db938c4ec34bdd4d99b1445db98bc0a61ec851".hexToBytes(),
*uriOut
)
}
9 changes: 9 additions & 0 deletions casper/src/rust/casper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,12 @@ pub struct CasperShardConf {
pub synchrony_finalized_baseline_enabled: bool,
pub synchrony_finalized_baseline_max_distance: u64,
pub max_user_deploys_per_block: u32,
/// Native token metadata baked into the TokenMetadata contract at genesis.
/// Present on every node (joiner, validator, ceremony master, observer, standalone)
/// so each path can log the effective values at startup.
pub native_token_name: String,
pub native_token_symbol: String,
pub native_token_decimals: u32,
}

impl CasperShardConf {
Expand Down Expand Up @@ -336,6 +342,9 @@ impl CasperShardConf {
synchrony_finalized_baseline_enabled: true,
synchrony_finalized_baseline_max_distance: 2048,
max_user_deploys_per_block: 32,
native_token_name: "F1R3CAP".to_string(),
native_token_symbol: "F1R3".to_string(),
native_token_decimals: 8,
}
}
}
Expand Down
55 changes: 55 additions & 0 deletions casper/src/rust/casper_conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,61 @@ pub struct GenesisBlockData {

#[serde(rename = "pos-multi-sig-quorum")]
pub pos_multi_sig_quorum: u32,

/// Full display name of the native token. Substituted into the
/// TokenMetadata Rholang contract at genesis and registered at
/// `rho:system:tokenMetadata`. Immutable after genesis.
#[serde(rename = "native-token-name")]
pub native_token_name: String,

/// Ticker symbol of the native token. Immutability rules are identical
/// to `native-token-name`. Operators MUST set this in config before genesis.
#[serde(rename = "native-token-symbol")]
pub native_token_symbol: String,

/// Number of decimal places used to display the native token
/// (1 token = 10^decimals dust). Immutability rules are identical to
/// `native-token-name`. Operators MUST set this in config before genesis.
#[serde(rename = "native-token-decimals")]
pub native_token_decimals: u32,
}

/// Maximum decimal places accepted for native token. Matches the de-facto
/// ERC-20 standard (ETH uses 18). Values above 18 exceed IEEE-754 double
/// safe-integer range (2^53), which breaks every JavaScript-based wallet
/// and block explorer. No production blockchain uses more than 18
/// (BTC=8, SOL=9, ATOM=6, DOT=10, KSM=12, ETH=18).
pub const MAX_NATIVE_TOKEN_DECIMALS: u32 = 18;

impl GenesisBlockData {
/// Validates native-token-* fields. Called during config load so a
/// misconfigured node fails startup loudly rather than baking bad
/// values into genesis or serving misleading metadata via `/api/status`.
pub fn validate_native_token(&self) -> Result<(), String> {
if self.native_token_name.trim().is_empty() {
return Err(format!(
"native-token-name must be non-empty and non-whitespace; got {:?}",
self.native_token_name
));
}
if self.native_token_symbol.trim().is_empty() {
return Err(format!(
"native-token-symbol must be non-empty and non-whitespace; got {:?}",
self.native_token_symbol
));
}
if self.native_token_decimals > MAX_NATIVE_TOKEN_DECIMALS {
return Err(format!(
"native-token-decimals={} exceeds maximum of {} (industry standard; \
ETH=18, BTC=8, SOL=9, ATOM=6); values above {} exceed IEEE-754 \
double safe-integer range and break JavaScript clients",
self.native_token_decimals,
MAX_NATIVE_TOKEN_DECIMALS,
MAX_NATIVE_TOKEN_DECIMALS
));
}
Ok(())
}
}

/// Genesis ceremony configuration
Expand Down
6 changes: 6 additions & 0 deletions casper/src/rust/engine/approve_block_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ impl ApproveBlockProtocolFactory {
block_number: i64,
pos_multi_sig_public_keys: Vec<String>,
pos_multi_sig_quorum: u32,
native_token_name: String,
native_token_symbol: String,
native_token_decimals: u32,
runtime_manager: &mut RuntimeManager,
last_approved_block: Arc<Mutex<Option<ApprovedBlock>>>,
event_log: Option<F1r3flyEvents>,
Expand Down Expand Up @@ -187,6 +190,9 @@ impl ApproveBlockProtocolFactory {
vaults,
supply: i64::MAX,
version: 1,
native_token_name,
native_token_symbol,
native_token_decimals,
};

let genesis_block = Genesis::create_genesis_block(runtime_manager, &genesis).await?;
Expand Down
18 changes: 18 additions & 0 deletions casper/src/rust/engine/block_approver_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ pub struct BlockApproverProtocol<T: TransportLayer + Send + Sync + 'static> {
pub required_sigs: i32,
pub pos_multi_sig_public_keys: Vec<String>,
pub pos_multi_sig_quorum: u32,
pub native_token_name: String,
pub native_token_symbol: String,
pub native_token_decimals: u32,

// Infrastructure
transport: Arc<T>,
Expand All @@ -60,6 +63,9 @@ impl<T: TransportLayer + Send + Sync + 'static> BlockApproverProtocol<T> {
required_sigs: i32,
pos_multi_sig_public_keys: Vec<String>,
pos_multi_sig_quorum: u32,
native_token_name: String,
native_token_symbol: String,
native_token_decimals: u32,
transport: Arc<T>,
conf: Arc<RPConf>,
) -> Result<Self, CasperError> {
Expand Down Expand Up @@ -94,6 +100,9 @@ impl<T: TransportLayer + Send + Sync + 'static> BlockApproverProtocol<T> {
required_sigs,
pos_multi_sig_public_keys,
pos_multi_sig_quorum,
native_token_name,
native_token_symbol,
native_token_decimals,
transport,
conf,
})
Expand Down Expand Up @@ -145,6 +154,9 @@ impl<T: TransportLayer + Send + Sync + 'static> BlockApproverProtocol<T> {
shard_id: &str,
pos_multi_sig_public_keys: &[String],
pos_multi_sig_quorum: u32,
native_token_name: &str,
native_token_symbol: &str,
native_token_decimals: u32,
) -> Result<(), String> {
// Basic checks – required sigs, absence of system deploys, bonds equality
if candidate.required_sigs < required_sigs {
Expand Down Expand Up @@ -208,6 +220,9 @@ impl<T: TransportLayer + Send + Sync + 'static> BlockApproverProtocol<T> {
vaults,
i64::MAX,
shard_id,
native_token_name,
native_token_symbol,
native_token_decimals,
);

let block_deploys: &Vec<ProcessedDeploy> = &block.body.deploys;
Expand Down Expand Up @@ -300,6 +315,9 @@ impl<T: TransportLayer + Send + Sync + 'static> BlockApproverProtocol<T> {
shard_id,
&self.pos_multi_sig_public_keys,
self.pos_multi_sig_quorum,
&self.native_token_name,
&self.native_token_symbol,
self.native_token_decimals,
)
.await
}
Expand Down
54 changes: 54 additions & 0 deletions casper/src/rust/engine/casper_launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ impl<T: TransportLayer + Send + Sync + Clone + 'static> CasperLaunchImpl<T> {
synchrony_finalized_baseline_max_distance: conf
.synchrony_finalized_baseline_max_distance,
max_user_deploys_per_block: conf.max_user_deploys_per_block,
native_token_name: conf.genesis_block_data.native_token_name.clone(),
native_token_symbol: conf.genesis_block_data.native_token_symbol.clone(),
native_token_decimals: conf.genesis_block_data.native_token_decimals,
};

Self {
Expand Down Expand Up @@ -332,6 +335,7 @@ impl<T: TransportLayer + Send + Sync + Clone + 'static> CasperLaunchImpl<T> {
);

let ab = approved_block.candidate.block.clone();
let genesis_post_state_hash = ab.body.state.post_state_hash.clone();

let casper = self.create_casper(validator_id.clone(), ab)?;
let casper_arc = Arc::new(casper);
Expand Down Expand Up @@ -409,12 +413,41 @@ impl<T: TransportLayer + Send + Sync + Clone + 'static> CasperLaunchImpl<T> {
)
.await?;

// Guard against config drift: a joiner's local native-token-* values
// must match what this network actually baked into the TokenMetadata
// contract at genesis. If they disagree, the node's /api/status would
// advertise values that contradict on-chain state, which misleads
// block explorers and wallets.
let runtime_manager = self.runtime_manager.lock().await;
crate::rust::util::token_metadata_check::verify_token_metadata_matches_config(
&runtime_manager,
&genesis_post_state_hash,
&self.conf.genesis_block_data.native_token_name,
&self.conf.genesis_block_data.native_token_symbol,
self.conf.genesis_block_data.native_token_decimals,
)
.await?;

Ok(())
}

async fn connect_as_genesis_validator(&self) -> Result<(), CasperError> {
println!("connectAsGenesisValidator");

// As a genesis validator, native-token-* values from local config are
// what will be baked into the TokenMetadata contract at genesis (via
// default_blessed_terms). On-chain state cannot disagree with local
// config here by construction, so no post-genesis verification is
// performed on this path.
tracing::info!(
event = "native_token_metadata_startup",
role = "genesis_validator",
native_token_name = %self.conf.genesis_block_data.native_token_name,
native_token_symbol = %self.conf.genesis_block_data.native_token_symbol,
native_token_decimals = self.conf.genesis_block_data.native_token_decimals,
"Genesis validator: native token metadata will be derived from local config"
);

let timestamp = self
.conf
.genesis_block_data
Expand Down Expand Up @@ -461,6 +494,9 @@ impl<T: TransportLayer + Send + Sync + Clone + 'static> CasperLaunchImpl<T> {
.pos_multi_sig_public_keys
.clone(),
self.conf.genesis_block_data.pos_multi_sig_quorum,
self.conf.genesis_block_data.native_token_name.clone(),
self.conf.genesis_block_data.native_token_symbol.clone(),
self.conf.genesis_block_data.native_token_decimals,
self.transport_layer.clone(),
Arc::new(self.rp_conf_ask.clone()),
)?;
Expand Down Expand Up @@ -501,6 +537,21 @@ impl<T: TransportLayer + Send + Sync + Clone + 'static> CasperLaunchImpl<T> {
self.conf.validator_private_key.as_deref(),
);

// As ceremony master, native-token-* values from local config will be
// baked into the TokenMetadata contract at genesis (via
// default_blessed_terms). On-chain state matches local config by
// construction on this path, so no post-genesis verification is
// performed. If your chain should use different values, update
// casper.genesis-block-data.native-token-* before genesis.
tracing::info!(
event = "native_token_metadata_startup",
role = "ceremony_master",
native_token_name = %self.conf.genesis_block_data.native_token_name,
native_token_symbol = %self.conf.genesis_block_data.native_token_symbol,
native_token_decimals = self.conf.genesis_block_data.native_token_decimals,
"Ceremony master: native token metadata will be baked into genesis from local config"
);

tracing::warn!("=== BOOTSTRAP GENESIS INPUT DEBUG START ===");

tracing::warn!(bonds_file = %self.conf.genesis_block_data.bonds_file);
Expand Down Expand Up @@ -554,6 +605,9 @@ impl<T: TransportLayer + Send + Sync + Clone + 'static> CasperLaunchImpl<T> {
.pos_multi_sig_public_keys
.clone(),
self.conf.genesis_block_data.pos_multi_sig_quorum,
self.conf.genesis_block_data.native_token_name.clone(),
self.conf.genesis_block_data.native_token_symbol.clone(),
self.conf.genesis_block_data.native_token_decimals,
&mut *self.runtime_manager.lock().await,
self.last_approved_block.clone(),
Some(self.event_publisher.clone()),
Expand Down
17 changes: 17 additions & 0 deletions casper/src/rust/engine/initializing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,7 @@ impl<T: TransportLayer + Send + Sync + Clone> Initializing<T> {
approved_block: &ApprovedBlock,
) -> Result<(), CasperError> {
let ab = approved_block.candidate.block.clone();
let genesis_post_state_hash = ab.body.state.post_state_hash.clone();

// RuntimeManager is now Arc<Mutex<RuntimeManager>>, so we clone the Arc
let runtime_manager = self.runtime_manager.clone();
Expand Down Expand Up @@ -969,6 +970,22 @@ impl<T: TransportLayer + Send + Sync + Clone> Initializing<T> {
"create_casper_and_transition_to_running: transition_to_running completed successfully"
);

// Guard joiners (first-time connections requesting an approved block from
// peers) against config drift: the node's local native-token-* values
// must match what this network baked into the TokenMetadata contract at
// genesis. See casper/src/rust/util/token_metadata_check.rs for details.
{
let runtime_manager = self.runtime_manager.lock().await;
crate::rust::util::token_metadata_check::verify_token_metadata_matches_config(
&runtime_manager,
&genesis_post_state_hash,
&self.casper_shard_conf.native_token_name,
&self.casper_shard_conf.native_token_symbol,
self.casper_shard_conf.native_token_decimals,
)
.await?;
}

self.transport_layer
.send_fork_choice_tip_request(&self.connections_cell, &self.rp_conf_ask)
.await
Expand Down
Loading
Loading