From 757425a3605096967c0cb6756de67fb1cda7ce76 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 2 Dec 2019 18:51:54 -0700 Subject: [PATCH] Add validator timestamp oracle proposal (#7159) * Add validator timestamp oracle proposal * Make timestamping part of the Vote program * Describe extending Vote to include timestamp: Option * Qualify getBlockTime-eligible blocks as rooted --- book/src/SUMMARY.md | 1 + .../proposals/validator-timestamp-oracle.md | 129 ++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 book/src/proposals/validator-timestamp-oracle.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 2bc3d5bae..9410d3d67 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -58,6 +58,7 @@ * [Snapshot Verification](proposals/snapshot-verification.md) * [Bankless Leader](proposals/bankless-leader.md) * [Durable Transaction Nonces](proposals/durable-tx-nonces.md) + * [Validator Timestamp Oracle](proposals/validator-timestamp-oracle.md) * [Implemented Design Proposals](implemented-proposals/README.md) * [Blocktree](implemented-proposals/blocktree.md) * [Cluster Software Installation and Updates](implemented-proposals/installer.md) diff --git a/book/src/proposals/validator-timestamp-oracle.md b/book/src/proposals/validator-timestamp-oracle.md new file mode 100644 index 000000000..7ab773e91 --- /dev/null +++ b/book/src/proposals/validator-timestamp-oracle.md @@ -0,0 +1,129 @@ +# Validator Timestamp Oracle + +Third-party users of Solana sometimes need to know the real-world time a block +was produced, generally to meet compliance requirements for external auditors or +law enforcement. This proposal describes a validator timestamp oracle that +would allow a Solana cluster to satisfy this need. + +The general outline of the proposed implementation is as follows: + +- At regular intervals, each validator records its observed time for a known slot + on-chain (via a Timestamp added to a slot Vote) +- A client can request a block time for a rooted block using the `getBlockTime` +RPC method. When a client requests a timestamp for block N: + + 1. A validator determines a "cluster" timestamp for a recent timestamped slot + before block N by observing all the timestamped Vote instructions recorded on + the ledger that reference that slot, and determining the stake-weighted mean + timestamp. + + 2. This recent mean timestamp is then used to calculate the timestamp of + block N using the cluster's established slot duration + +Requirements: +- Any validator replaying the ledger in the future must come up with the same + time for every block since genesis +- Estimated block times should not drift more than an hour or so before resolving + to real-world (oracle) data +- The block times are not controlled by a single centralized oracle, but + ideally based on a function that uses inputs from all validators +- Each validator must maintain a timestamp oracle + +The same implementation can provide a timestamp estimate for a not-yet-rooted +block. However, because the most recent timestamped slot may or may not be +rooted yet, this timestamp would be unstable (potentially failing requirement +1). Initial implementation will target rooted blocks, but if there is a use case +for recent-block timestamping, it will be trivial to add the RPC apis in the +future. + +## Recording Time + +At regular intervals as it is voting on a particular slot, each validator +records its observed time by including a timestamp in its Vote instruction +submission. The corresponding slot for the timestamp is the newest Slot in the +Vote vector (`Vote::slots.iter().max()`). It is signed by the validator's +identity keypair as a usual Vote. In order to enable this reporting, the Vote +struct needs to be extended to include a timestamp field, `timestamp: +Option`, which will be set to `None` in most Votes. + +This proposal suggests that Vote instructions with `Some(timestamp)` be issued +every 30min, which should be short enough to prevent block times drifting very +much, without adding too much transaction overhead to the cluster. Validators +can convert this time to a slot interval using the `slots_per_year` value that +is stored in each bank. + +```text +let seconds_in_30min = 1800; +let timestamp_interval = (slots_per_year / SECONDS_PER_YEAR) * seconds_in_30min; +``` + +Votes with `Some(timestamp)` should be triggered in `replay_stage::handle_votable_bank()` +when `bank.slot() % timestamp_interval == 0`. + +### Vote Accounts + +A validator's vote account will hold its most recent slot-timestamp in VoteState. + +### Vote Program + +The on-chain Vote program needs to be extended to process a timestamp sent with +a Vote instruction from validators. In addition to its current process_vote +functionality (including loading the correct Vote account and verifying that the +transaction signer is the expected validator), this process needs to compare the +timestamp and corresponding slot to the currently stored values to verify that +they are both monotonically increasing, and store the new slot and timestamp in +the account. + +## Calculating Stake-Weighted Mean Timestamp + +In order to calculate the estimated timestamp for a particular block, a +validator first needs to identify the most recently timestamped slot: + +```text +let timestamp_slot = floor(current_slot / timestamp_interval); +``` + +Then the validator needs to gather all Vote WithTimestamp transactions from the +ledger that reference that slot, using `Blocktree::get_slot_entries()`. As these +transactions could have taken some time to reach and be processed by the leader, +the validator needs to scan several completed blocks after the timestamp_slot to +get a reasonable set of Timestamps. The exact number of slots will need to be +tuned: More slots will enable greater cluster participation and more timestamp +datapoints; fewer slots will speed how long timestamp filtering takes. + +From this collection of transactions, the validator calculates the +stake-weighted mean timestamp, cross-referencing the epoch stakes from +`staking_utils::staked_nodes_at_epoch()`. + +Any validator replaying the ledger should derive the same stake-weighted mean +timestamp by processing the Timestamp transactions from the same number of +slots. + +## Calculating Estimated Time for a Particular Block + +Once the mean timestamp for a known slot is calculated, it is trivial to +calculate the estimated timestamp for subsequent block N: + +```text +let block_n_timestamp = mean_timestamp + (block_n_slot_offset * slot_duration); +``` + +where `block_n_slot_offset` is the difference between the slot of block N and +the timestamp_slot, and `slot_duration` is derived from the cluster's +`slots_per_year` stored in each Bank + +## Additional Considerations + +1. This proposal puts a distinct processing burden on a validator when it receives +a `getBlockTime` RPC request. Another approach would be for a validator to +maintain a list of mean timestamps, populated as the validator +builds/replays the ledger. + +2. This proposal doesn't address the 4th requirement, that each validator must +maintain a timestamp oracle. Even though timestamps are tied to voting, there +are no lockout implications if a validator does not ever included timestamps +with Votes. One sociological approach would be to include timestamp expectations +in validator uptime calculations, where uptime is redefined as a function of a +validator both promptly submitting votes and timestamps over an epoch. This +might require storing more information about historical timestamps in the vote +account, however.