A validator votes on a PoH hash for two purposes. First, the vote indicates it believes the ledger is valid up until that point in time. Second, since many valid forks may exist at a given height, the vote also indicates exclusive support for the fork. This document describes only the former. The latter is described in Tower BFT.
To start voting, a validator first registers an account to which it will send its votes. It then sends votes to that account. The vote contains the tick height of the block it is voting on. The account stores the 32 highest heights.
Only the validator knows how to find its own votes directly.
Other components, such as the one that calculates confirmation time, needs to be baked into the validator code. The validator code queries the bank for all accounts owned by the vote program.
Voting ballots do not contain a PoH hash. The validator is only voting that it has observed an arbitrary block at some height.
Voting ballots do not contain a hash of the bank state. Without that hash, there is no evidence that the validator executed the transactions and verified there were no double spends.
At the moment a block is produced, the leader shall add a NewBlock transaction to the ledger with a number of tokens that represents the validation reward. It is effectively an incremental multisig transaction that sends tokens from the mining pool to the validators. The account should allocate just enough space to collect the votes required to achieve a supermajority. When a validator observes the NewBlock transaction, it has the option to submit a vote that includes a hash of its ledger state (the bank state). Once the account has sufficient votes, the vote program should disperse the tokens to the validators, which causes the account to be deleted.
The bank will need to be aware of the vote program. After each transaction, it should check if it is a vote transaction and if so, check the state of that account. If the transaction caused the supermajority to be achieved, it should log the time since the NewBlock transaction was submitted.
Tower BFT is the proposed fork selection algorithm. It proposes that payment to miners be postponed until the stack of validator votes reaches a certain depth, at which point rollback is not economically feasible. The vote program may therefore implement Tower BFT. Vote instructions would need to reference a global Tower account so that it can track cross-block state.
Using programs and accounts to implement this is a bit tedious. The hardest part is figuring out how much space to allocate in NewBlock. The two variables are the active set and the stakes of those validators. If we calculate the active set at the time NewBlock is submitted, the number of validators to allocate space for is known upfront. If, however, we allow new validators to vote on old blocks, then we'd need a way to allocate space dynamically.
Similar in spirit, if the leader caches stakes at the time of NewBlock, the vote program doesn't need to interact with the bank when it processes votes. If we don't, then we have the option to allow stakes to float until a vote is submitted. A validator could conceivably reference its own staking account, but that'd be the current account value instead of the account value of the most recently finalized bank state. The bank currently doesn't offer a means to reference accounts from particular points in time.
Does a vote on one height imply a vote on all blocks of lower heights of that fork? If it does, we'll need a way to lookup the accounts of all blocks that haven't yet reached supermajority. If not, the validator could send votes to all blocks explicitly to get the block rewards.