Skip to content
Open
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
10 changes: 6 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 17 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,9 @@ paste = "1.0"
prometheus = "0.13.4"
# Keep in sync with `solana-storage-proto` codegen.

# TODO: Update to the merged version when ready

solana-transaction-context = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "190146af2ac0890848b50e8fc9d6c926c8205b5e", features = [
solana-transaction-context = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "ab02436a458bf83aade6a8b9276f6384077c78fc", features = [
"dev-context-only-utils",
] }
solana-transaction-error = { version = "3.0" }
Expand Down Expand Up @@ -194,7 +195,8 @@ serde_json = "1.0"
serde_with = "3.16"
serial_test = "3.2"
sha3 = "0.10.8"
solana-account = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "190146af2ac0890848b50e8fc9d6c926c8205b5e" }
# TODO: Update to the merged version when ready
solana-account = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "ab02436a458bf83aade6a8b9276f6384077c78fc" }
solana-account-decoder = { version = "4.0" }
solana-account-decoder-client-types = { version = "4.0" }
solana-account-info = { version = "3.1" }
Expand Down Expand Up @@ -246,7 +248,8 @@ solana-program = "3.0"
solana-program-error = { version = "3.0" }
solana-program-option = { version = "3.0" }
solana-program-pack = { version = "3.0" }
solana-program-runtime = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "190146af2ac0890848b50e8fc9d6c926c8205b5e" }
# TODO: Update to the merged version when ready
solana-program-runtime = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "ab02436a458bf83aade6a8b9276f6384077c78fc" }
solana-pubkey = { version = "4.1" }
solana-pubsub-client = { version = "4.0" }
solana-rent = { version = "3.0" }
Expand All @@ -270,22 +273,28 @@ solana-system-program = { version = "=4.0.0", features = [
] }
solana-system-transaction = { version = "3.0" }
solana-sysvar = { version = "3.0" }
solana-sysvar-id = { version = "3.0" }
solana-timings = { package = "solana-svm-timings", version = "=4.0.0", features = [
"agave-unstable-api",
] }
solana-transaction = { version = "3.0" }

# TODO: Update to the merged version when ready
[workspace.dependencies.solana-svm]
features = ["dev-context-only-utils"]
git = "https://github.com/magicblock-labs/magicblock-svm.git"
rev = "190146af2ac0890848b50e8fc9d6c926c8205b5e"
rev = "ab02436a458bf83aade6a8b9276f6384077c78fc"

[patch.crates-io]
solana-account = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "190146af2ac0890848b50e8fc9d6c926c8205b5e" }
solana-program-runtime = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "190146af2ac0890848b50e8fc9d6c926c8205b5e" }
# TODO: Update to the merged version when ready
solana-account = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "ab02436a458bf83aade6a8b9276f6384077c78fc" }
# TODO: Update to the merged version when ready
solana-program-runtime = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "ab02436a458bf83aade6a8b9276f6384077c78fc" }
solana-storage-proto = { path = "storage-proto" }
solana-svm = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "190146af2ac0890848b50e8fc9d6c926c8205b5e" }
solana-transaction-context = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "190146af2ac0890848b50e8fc9d6c926c8205b5e" }
# TODO: Update to the merged version when ready
solana-svm = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "ab02436a458bf83aade6a8b9276f6384077c78fc" }
# TODO: Update to the merged version when ready
solana-transaction-context = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "ab02436a458bf83aade6a8b9276f6384077c78fc" }
# Fork is used to enable `disable_manual_compaction` usage
# Fork is based on commit d4e9e16 of rocksdb (parent commit of 0.23.0 release)
# without patching update isn't possible due to conflict with solana deps
Expand Down
6 changes: 4 additions & 2 deletions magicblock-core/src/link/replication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ pub struct Block {
pub slot: Slot,
/// Blockhash for this slot.
pub hash: Hash,
/// Unix timestamp (seconds).
pub timestamp: i64,
/// Unix timestamp in milliseconds. The whole-second `Clock::unix_timestamp`
/// is derived from this; the sub-second remainder is the precision exposed
/// via the `HighPrecisionClock` sysvar.
pub timestamp_millis: i64,
}

/// Periodic checkpoint for state verification and catch-up.
Expand Down
17 changes: 12 additions & 5 deletions magicblock-ledger/src/blockstore_processor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,22 @@ async fn replay_blocks(
transactions: successfull_txs,
};

let Some(timestamp) = block.block_time else {
if block.block_time.is_none() {
return Err(LedgerError::BlockStoreProcessor(format!(
"Block has no timestamp, {block:?}",
)));
};
}
{
let block =
LatestBlockInner::new(block.slot, block.blockhash, timestamp);
ledger.latest_block().store(block);
// Restore the millisecond precision so the HighPrecisionClock
// sysvar is reproduced exactly during replay.
let latest = LatestBlockInner::new_with_millis(
block.slot,
block.blockhash,
ledger
.get_block_time_millis(block.slot)?
.unwrap_or_default(),
);
ledger.latest_block().store(latest);
}
// Transactions are stored in the ledger ordered by most recent to latest
// such to replay them in the order they executed we need to reverse them
Expand Down
8 changes: 7 additions & 1 deletion magicblock-ledger/src/database/columns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,14 @@ pub struct SlotSignatures;

/// The block time column
///
/// Stores the block timestamp as **milliseconds** since the Unix epoch. The
/// sub-second remainder preserves the precision exposed via the
/// `HighPrecisionClock` sysvar so it can be reproduced deterministically during
/// ledger replay; [`Ledger::get_block_time`] rounds it down to whole seconds
/// for consumers that expect a [`UnixTimestamp`] (RPC `getBlockTime`, etc.).
///
/// * index type: `u64` (see [`SlotColumn`])
/// * value type: [`UnixTimestamp`]
/// * value type: [`UnixTimestamp`] (milliseconds, despite the type name)
pub struct Blocktime;

/// The block hash column
Expand Down
16 changes: 15 additions & 1 deletion magicblock-ledger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub struct LatestBlockInner {
pub slot: u64,
pub blockhash: Hash,
pub clock: Clock,
pub timestamp_millis: i64,
}

/// Atomically updated, shared, latest block information
Expand All @@ -36,16 +37,29 @@ pub struct LatestBlock {
}

impl LatestBlockInner {
/// Creates a block from a whole-second timestamp. The millisecond timestamp
/// is that value with a zero sub-second component.
pub fn new(slot: u64, blockhash: Hash, timestamp: i64) -> Self {
Self::new_with_millis(slot, blockhash, timestamp * 1000)
}

/// Creates a block from a millisecond-accurate timestamp, deriving the
/// whole-second `Clock::unix_timestamp` from it.
pub fn new_with_millis(
slot: u64,
blockhash: Hash,
timestamp_millis: i64,
) -> Self {
let clock = Clock {
slot: slot + 1,
unix_timestamp: timestamp,
unix_timestamp: timestamp_millis.div_euclid(1000),
..Default::default()
};
Self {
slot,
blockhash,
clock,
timestamp_millis,
}
}
}
Expand Down
74 changes: 70 additions & 4 deletions magicblock-ledger/src/store/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,13 @@ impl Ledger {
latest_block,
};
let (slot, blockhash) = ledger.get_max_blockhash()?;
let time = ledger.get_block_time(slot)?.unwrap_or_default();
let block = LatestBlockInner::new(slot, blockhash, time);
let timestamp_millis =
ledger.get_block_time_millis(slot)?.unwrap_or_default();
let block = LatestBlockInner::new_with_millis(
slot,
blockhash,
timestamp_millis,
);
ledger.latest_block.store(block);
ledger.initialize_lowest_cleanup_slot()?;

Expand Down Expand Up @@ -279,6 +284,19 @@ impl Ledger {
&self,
slot: Slot,
) -> LedgerResult<Option<UnixTimestamp>> {
let _lock = self.check_lowest_cleanup_slot(slot)?;
// The column stores milliseconds; consumers of this API expect whole
// seconds (RPC `getBlockTime`, block metadata, replay, ...).
Ok(self
.blocktime_cf
.get(slot)?
.map(|millis| millis.div_euclid(1000)))
}

pub(crate) fn get_block_time_millis(
&self,
slot: Slot,
) -> LedgerResult<Option<i64>> {
let _lock = self.check_lowest_cleanup_slot(slot)?;
self.blocktime_cf.get(slot)
}
Expand Down Expand Up @@ -368,8 +386,7 @@ impl Ledger {
// As far as we are concerned these are just the time when we advanced to
// a specific slot.
pub fn write_block(&self, block: LatestBlockInner) -> LedgerResult<()> {
self.blocktime_cf
.put(block.slot, &block.clock.unix_timestamp)?;
self.blocktime_cf.put(block.slot, &block.timestamp_millis)?;
self.blocktime_cf.try_increase_entry_counter(1);

self.blockhash_cf.put(block.slot, &block.blockhash)?;
Expand Down Expand Up @@ -2784,4 +2801,53 @@ mod tests {
let result = store.verify_transaction_signature(&real_sig).unwrap();
assert_eq!(result, None);
}

#[test]
fn test_block_time_millis_persist_and_reload() {
init_logger!();
let ledger_path = get_tmp_ledger_path_auto_delete!();

let slot = 7;
let secs = 1_700_000_000;
let millis = secs * 1000 + 123;

{
let store = Ledger::open(ledger_path.path()).unwrap();
let block = LatestBlockInner::new_with_millis(
slot,
Hash::new_unique(),
millis,
);
store.write_block(block).unwrap();

// The single Blocktime column holds the full millisecond timestamp,
assert_eq!(
store.get_block_time_millis(slot).unwrap(),
Some(millis)
);
// ...which reads back as whole seconds for second-precision consumers.
assert_eq!(store.get_block_time(slot).unwrap(), Some(secs));
assert_eq!(store.latest_block().load().timestamp_millis, millis);
assert_eq!(store.latest_block().load().clock.unix_timestamp, secs);
}

// Reopening reconstructs the millisecond precision for the tip block.
{
let store = Ledger::open(ledger_path.path()).unwrap();
assert_eq!(store.latest_block().load().timestamp_millis, millis);
assert_eq!(store.latest_block().load().clock.unix_timestamp, secs);
}
}

#[test]
fn test_block_time_absent_returns_none() {
init_logger!();
let ledger_path = get_tmp_ledger_path_auto_delete!();
let store = Ledger::open(ledger_path.path()).unwrap();

// A slot with no recorded block time reports no time in either form.
let slot = 3;
assert_eq!(store.get_block_time(slot).unwrap(), None);
assert_eq!(store.get_block_time_millis(slot).unwrap(), None);
}
}
11 changes: 6 additions & 5 deletions magicblock-magic-program-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@ edition.workspace = true

[features]
default = []
backward-compat = [
"dep:solana-program-compat",
"dep:solana-signature-compat",
]
backward-compat = ["dep:solana-program-compat", "dep:solana-signature-compat"]

[dependencies]
solana-program = { workspace = true }
solana-program-compat = { package = "solana-program", version = ">=2.0, <3", default-features = false, optional = true }
solana-signature = { workspace = true, features = ["serde"] }
solana-signature-compat = { package = "solana-signature", version = ">=2.0, <3", default-features = false, features = ["serde"], optional = true }
solana-signature-compat = { package = "solana-signature", version = ">=2.0, <3", default-features = false, features = [
"serde",
], optional = true }
solana-sysvar = { workspace = true }
solana-sysvar-id = { workspace = true }
bincode = "^1.3.3"
serde = { version = "^1.0.228", features = ["derive"] }
const-crypto = "0.3.0"
1 change: 1 addition & 0 deletions magicblock-magic-program-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod compat;
pub mod instruction;
pub mod pda;
pub mod response;
pub mod sysvar;

pub use compat::{declare_id, pubkey, Pubkey};

Expand Down
Loading
Loading