From fb79a8da6ce4ee3b80767668c85123ee2ff44d41 Mon Sep 17 00:00:00 2001 From: Henri S Date: Fri, 11 Oct 2019 08:19:30 +0300 Subject: [PATCH] ticket sampling coded. --- src/algorithms/expected_consensus.md | 4 ++ .../filecoin_blockchain/base_blockchain.id | 1 - .../filecoin_blockchain/blockchain/block.go | 72 +++++++++++++------ .../filecoin_blockchain/blockchain/block.id | 16 ++--- .../blockchain/block_syncer.go | 2 + .../storage_power_consensus/_index.md | 64 ++++++++++++++++- .../storage_power_consensus_subsystem.go | 22 +++--- .../storage_power_consensus_subsystem.id | 11 +-- .../storage_mining_subsystem.go | 4 +- 9 files changed, 149 insertions(+), 47 deletions(-) diff --git a/src/algorithms/expected_consensus.md b/src/algorithms/expected_consensus.md index 4a6c0bdaa..064347e9e 100644 --- a/src/algorithms/expected_consensus.md +++ b/src/algorithms/expected_consensus.md @@ -63,6 +63,10 @@ However this introduces a tradeoff: decreasing the cost of running a targeted attack (given they have local predictability). - It means electionProofs are stored separately from new tickets on a block, taking up more space on-chain. + +How is K selected? +- On the one end, there is no advantage to picking K larger than finality (by definition a fully-global lottery). +- On the other, making K smaller reduces adversarial power to grind. ``` ### Ticket generation diff --git a/src/systems/filecoin_blockchain/base_blockchain.id b/src/systems/filecoin_blockchain/base_blockchain.id index e9a4353f2..217733b10 100644 --- a/src/systems/filecoin_blockchain/base_blockchain.id +++ b/src/systems/filecoin_blockchain/base_blockchain.id @@ -22,7 +22,6 @@ type ElectionProof struct { } type ChainWeight UVarint -type ChainHeight UVarint type Epoch UVarint type ElectionNonce UVarint diff --git a/src/systems/filecoin_blockchain/blockchain/block.go b/src/systems/filecoin_blockchain/blockchain/block.go index 38ae3d377..93b67bdbf 100644 --- a/src/systems/filecoin_blockchain/blockchain/block.go +++ b/src/systems/filecoin_blockchain/blockchain/block.go @@ -13,28 +13,21 @@ func SmallerBytes(a, b util.Bytes) util.Bytes { return a } -// func (block *Block_I) ExtractElectionSeed() base.ElectionSeed { -// panic("TODO") -// var ret []byte - -// for _, currBlock := range lookbackTipset.Blocks() { -// for _, currTicket := range currBlock.Tickets() { - -// currSeed := Hash( -// HashRole_ElectionSeedFromVRFOutput, -// currTicket.VRFResult().bytes(), -// ) -// if ret == nil { -// ret = currSeed -// } else { -// ret = SmallerBytes(currSeed, ret) -// } -// } -// } +func (ts *Tipset_I) MinTicket() base.Ticket { + var ret Ticket -// Assert(ret != nil) -// return ElectionSeed.FromBytesInternal(nil, ret) -// } + for _, currBlock := range ts.Blocks() { + tix := currBlock.Ticket() + if ret == nil { + ret = tix + } else { + ret = SmallerBytes(tix, ret) + } + } + + Assert(ret != nil) + return tix +} func (ts *Tipset_I) ValidateSyntax() bool { @@ -89,6 +82,41 @@ func (ts *Tipset_I) LatestTimestamp() clock.Time { // return currTree // } +func (bl *Block_I) TipsetAtEpoch(epoch Epoch) Tipset_I { + dist := bl.Epoch() - epoch - 1 + current := bl.ParentTipset_ + parents := current.Parents_ + for i := range dist { + current = parents + parent = current.Parents_ + } + + return current +} + +func (bl *Block_I) TicketAtEpoch(epoch base.Epoch) base.Ticket_I { + ts := bl.TipsetAtEpoch(epoch) + return ts.MinTicket() +} + +func (chain *Chain_I) TipsetAtEpoch(epoch base.Epoch) base.Tipset_I { + dist := chain.HeadEpoch() - epoch + current := chain.HeadTipset() + parents := current.Parents_ + for i := range dist { + current = parents + parent = current.Parents_ + } + + return current +} + func (chain *Chain_I) TicketAtEpoch(epoch base.Epoch) base.Ticket_I { - return chain.ticketChain.TicketAtEpoch(epoch) + ts := chain.TipsetAtEpoch(epoch) + return ts.MinTicket() +} + +func (chain *Chain_I) FinalizedEpoch() Epoch { + ep := HeadEpoch() + return ep - spc.GetFinality() } diff --git a/src/systems/filecoin_blockchain/blockchain/block.id b/src/systems/filecoin_blockchain/blockchain/block.id index 2207915b0..cdfdf4fef 100644 --- a/src/systems/filecoin_blockchain/blockchain/block.id +++ b/src/systems/filecoin_blockchain/blockchain/block.id @@ -9,7 +9,7 @@ type Block struct { ElectionProof base_blockchain.ElectionProof ParentTipset Tipset ParentWeight base_blockchain.ChainWeight - Height base_blockchain.ChainHeight @(cached) + Height Epoch StateTree stateTree.StateTree Messages [base_blockchain.Message] // BLSAggregate filcrypto.Signature @@ -20,6 +20,9 @@ type Block struct { // SerializeSigned() []byte // ComputeUnsignedFingerprint() [] + + TipsetAtEpoch(epoch Epoch) Tipset + TicketAtEpoch(epoch Epoch) base_blockchain.Ticket } type SignedBlock struct { @@ -34,26 +37,21 @@ type Tipset struct { Parents Tipset @(cached) // StateTree should live in VM StateTree stateTree.StateTree @(cached) - Epoch @(cached) + Epoch Epoch @(cached) Weight base_blockchain.ChainWeight @(cached) ValidateSyntax() bool @(cached) LatestTimestamp() clock.Time @(cached) + MinTicket() Ticket @(cached) } type Chain struct { + HeadTipset() Tipset HeadEpoch() Epoch FinalizedEpoch() Epoch LatestCheckpoint() Epoch AddTipset(ts Tipset) - ticketchain TicketChain TicketAtEpoch(epoch Epoch) Ticket TipsetAtEpoch(epoch Epoch) Tipset -} - -type TicketChain struct { - associatedChain &Chain - TicketAtEpoch(epoch Epoch) Ticket - AddTipset(ts Tipset) } \ No newline at end of file diff --git a/src/systems/filecoin_blockchain/blockchain/block_syncer.go b/src/systems/filecoin_blockchain/blockchain/block_syncer.go index 7f1372bb7..c9b43a0e8 100644 --- a/src/systems/filecoin_blockchain/blockchain/block_syncer.go +++ b/src/systems/filecoin_blockchain/blockchain/block_syncer.go @@ -14,6 +14,8 @@ func (self *BlockSyncer_I) OnNewBlock(block Block) error { // The syntactic stage may be validated without reference to additional data (see block) func (bs *BlockSyncer_I) validateBlockSyntax(block Block) error { panic("TODO") + + // if !block.Epoch().WithinFinality // if !block.MinerAddress().VerifySyntax(StorageMinerActor.Address.Protocol()) { // return ErrInvalidBlockSyntax("bad miner address syntax") // } diff --git a/src/systems/filecoin_blockchain/storage_power_consensus/_index.md b/src/systems/filecoin_blockchain/storage_power_consensus/_index.md index 388463c86..0b1c912ef 100644 --- a/src/systems/filecoin_blockchain/storage_power_consensus/_index.md +++ b/src/systems/filecoin_blockchain/storage_power_consensus/_index.md @@ -94,4 +94,66 @@ Because a Tipset can contain multiple blocks, the smallest ticket in the Tipset ┴────┘─ ─ ─ ─ ─ ─ ─ ─ ┘ ``` -In the above diagram, a miner will use block A's Ticket to generate a new ticket (or an election proof farther in the future) since it is the smallest in the Tipset. \ No newline at end of file +In the above diagram, a miner will use block A's Ticket to generate a new ticket (or an election proof farther in the future) since it is the smallest in the Tipset. + +## Drawing randomness for sector commitments + +Tickets are used as input to the SEAL above in order to tie Proofs-of-Replication to a given chain, thereby preventing long-range attacks (from another miner in the future trying to reuse SEALs). + +The ticket has to be drawn from a finalized block in order to prevent the miner from potential losing storage (in case of a chain reorg) even though their storage is intact. + +Verification should ensure that the ticket was drawn no farther back than necessary by the miner. We note that tickets can uniquely be associated to a given round in the protocol (lest a hash collision be found), but that the round number is explicited by the miner in `commitSector`. + +We present precisely how ticket selection and verification should work. In the below, we use the following notation: + +- `F`-- Finality (number of rounds) +- `X`-- round in which SEALing starts +- `Z`-- round in which the SEAL appears (in a block) +- `Y`-- round announced in the SEAL `commitSector` (should be X, but a miner could use any Y <= X), denoted by the ticket selection + - `T`-- estimated time for SEAL, dependent on sector size + - `G = T + variance`-- necessary flexibility to account for network delay and SEAL-time variance. + +We expect Filecoin will be able to produce estimates for sector commitment time based on sector sizes, e.g.: +`(estimate, variance) <--- SEALTime(sectors)` +G and T will be selected using these. + +###### Picking a Ticket to Seal + +When starting to prepare a SEAL in round X, the miner should draw a ticket from X-F with which to compute the SEAL. + +###### Verifying a Seal's ticket + +When verifying a SEAL in round Z, a verifier should ensure that the ticket used to generate the SEAL is found in the range of rounds [Z-T-F-G, Z-T-F+G]. + +###### In Detail + +``` + Prover + ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + │ + + ▼ + X-F ◀───────F────────▶ X ◀──────────T─────────▶ Z + -G . +G . . + ───(┌───────┐)───────────────( )──────────────────────( )────────▶ + └───────┘ ' ' time + [Z-T-F-G, Z-T-F+G] + ▲ + + └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + Verifier +``` + +Note that the prover here is submitting a message on chain (i.e. the SEAL). Using an older ticket than necessary to generate the SEAL is something the miner may do to gain more confidence about finality (since we are in a probabilistically final system). However it has a cost in terms of securing the chain in the face of long-range attacks (specifically, by mixing in chain randomness here, we ensure that an attacker going back a month in time to try and create their own chain would have to completely regenerate any and all sectors drawing randomness since to use for their fork's power). + +We break this down as follows: + +- The miner should draw from `X-F`. +- The verifier wants to find what `X-F` should have been (to ensure the miner is not drawing from farther back) even though Y (i.e. the round of the ticket actually used) is an unverifiable value. +- Thus, the verifier will need to make an inference about what `X-F` is likely to have been based on: + - (known) round in which the message is received (Z) + - (known) finality value (F) + - (approximate) SEAL time (T) +- Because T is an approximate value, and to account for network delay and variance in SEAL time across miners, the verifier allows for G offset from the assumed value of `X-F`: `Z-T-F`, hence verifying that the ticket is drawn from the range `[Z-T-F-G, Z-T-F+G]`. + + diff --git a/src/systems/filecoin_blockchain/storage_power_consensus/storage_power_consensus_subsystem.go b/src/systems/filecoin_blockchain/storage_power_consensus/storage_power_consensus_subsystem.go index 6fedca774..465e29183 100644 --- a/src/systems/filecoin_blockchain/storage_power_consensus/storage_power_consensus_subsystem.go +++ b/src/systems/filecoin_blockchain/storage_power_consensus/storage_power_consensus_subsystem.go @@ -9,11 +9,13 @@ import ( clock "github.com/filecoin-project/specs/systems/filecoin_nodes/clock" ) +const FINALITY = 500 + const ( SPC_LOOKBACK_RANDOMNESS = 300 // this is EC.K maybe move it there. TODO SPC_LOOKBACK_TICKET = 1 // we chain blocks together one after the other - SPC_LOOKBACK_POST = -1 // TODO complete - SPC_LOOKBACK_SEAL = -1 // TODO complete + SPC_LOOKBACK_POST = 1 // cheap to generate, should be set as close to current TS as possible + SPC_LOOKBACK_SEAL = FINALITY // should be set to finality ) const VRFPersonalization { @@ -56,7 +58,7 @@ func (spc *StoragePowerConsensusSubsystem_I) ValidateBlock(block blockchain.Bloc } func (spc *StoragePowerConsensusSubsystem_I) validateTicket(ticket base.Ticket, pk filcrypto.PublicKey) bool { - T1 := storagePowerConsensus.GetTicketProductionSeed(sms.CurrentChain) + T1 := storagePowerConsensus.GetTicketProductionSeed(sms.CurrentChain, sms.Blockchain.LatestEpoch()) input := VRFPersonalization.Ticket input.append(T1.Output) return ticket.Verify(input, pk) @@ -70,25 +72,25 @@ func (spc *StoragePowerConsensusSubsystem_I) StoragePowerConsensusError(errMsg s panic("TODO") } -func (spc *StoragePowerConsensusSubsystem_I) GetTicketProductionSeed(chain blockchain.Chain) base_mining.SealSeed { +func (spc *StoragePowerConsensusSubsystem_I) GetTicketProductionSeed(chain blockchain.Chain, epoch Epoch) base_mining.SealSeed { return &base_mining.SealSeed_I { chain.TicketAtEpoch(epoch-SPC_LOOKBACK_TICKET) } } -func (spc *StoragePowerConsensusSubsystem_I) GetElectionProofSeed(chain blockchain.Chain) base_mining.SealSeed { +func (spc *StoragePowerConsensusSubsystem_I) GetElectionProofSeed(chain blockchain.Chain, epoch Epoch) base_mining.SealSeed { return &base_mining.SealSeed_I { chain.TicketAtEpoch(epoch-SPC_LOOKBACK_RANDOMNESS), } } -func (spc *StoragePowerConsensusSubsystem_I) GetSealSeed(chain blockchain.Chain) base_mining.SealSeed { +func (spc *StoragePowerConsensusSubsystem_I) GetSealSeed(chain blockchain.Chain, epoch Epoch) base_mining.SealSeed { return &base_mining.SealSeed_I { chain.TicketAtEpoch(epoch-SPC_LOOKBACK_SEAL) } } -func (spc *StoragePowerConsensusSubsystem_I) GetPoStChallenge(chain blockchain.Chain) base_mining.PoStChallenge { +func (spc *StoragePowerConsensusSubsystem_I) GetPoStChallenge(chain blockchain.Chain, epoch Epoch) base_mining.PoStChallenge { return &base_mining.PoStChallenge_I { chain.TicketAtEpoch(epoch-SPC_LOOKBACK_POST) } @@ -103,7 +105,7 @@ func (spc *StoragePowerConsensusSubsystem_I) ValidateElectionProof(height BlockH // 2. Determine that ticket was validly scratched minerPK := spc.PowerTable.GetMinerPublicKey(workerAddr) input := VRFPersonalization.ElectionProof - TK := storagePowerConsensus.GetElectionProofSeed(sms.CurrentChain) + TK := storagePowerConsensus.GetElectionProofSeed(sms.CurrentChain, sms.Blockchain.LatestEpoch()) input.append(TK.Output) input.appent(electionProof.ElectionNonce) @@ -121,6 +123,10 @@ func (spc *StoragePowerConsensusSubsystem_I) IsWinningElectionProof(electionProo return (minerPower * 2^(len(electionProof.Output)*8) < electionProof.Output * totalPower) } +func (spc *StoragePowerConsensusSubsystem_I) GetFinality() Epoch { + return FINALITY +} + // Power Table func (pt *PowerTable_I) GetMinerPower(addr base.Address) base.StoragePower { diff --git a/src/systems/filecoin_blockchain/storage_power_consensus/storage_power_consensus_subsystem.id b/src/systems/filecoin_blockchain/storage_power_consensus/storage_power_consensus_subsystem.id index 6e235d280..94444b454 100644 --- a/src/systems/filecoin_blockchain/storage_power_consensus/storage_power_consensus_subsystem.id +++ b/src/systems/filecoin_blockchain/storage_power_consensus/storage_power_consensus_subsystem.id @@ -29,16 +29,18 @@ type StoragePowerConsensusSubsystem struct {//(@mutable) // Randomness methods // call by StorageMiningSubsystem during block production - GetTicketProductionSeed(chain blockchain.Chain) base.Ticket + GetTicketProductionSeed(chain blockchain.Chain, epoch Epoch) base.Ticket // call by StorageMiningSubsystem during block production - GetElectionProofSeed(chain blockchain.Chain) base.Ticket + GetElectionProofSeed(chain blockchain.Chain, epoch Epoch) base.Ticket // call by StorageMiningSubsystem in sealing sector - GetSealSeed(chain blockchain.Chain) base_mining.SealSeed + GetSealSeed(chain blockchain.Chain, epoch Epoch) base_mining.SealSeed // call by StorageMiningSubsystem after sealing - GetPoStChallenge(chain blockchain.Chain) base_mining.PoStChallenge + GetPoStChallenge(chain blockchain.Chain, epoch Epoch) base_mining.PoStChallenge + + GetFinality() Epoch } type ExpectedConsensus struct { @@ -48,6 +50,7 @@ type ExpectedConsensus struct { type PowerTable struct { // all power here is always verified + RegisterMiner(addr base.Address) miners {base.Address: StorageMiner} GetMinerPower(addr base.Address) base.StoragePower GetTotalPower() base.StoragePower diff --git a/src/systems/filecoin_mining/storage_mining/storage_mining_subsystem.go b/src/systems/filecoin_mining/storage_mining/storage_mining_subsystem.go index 49dab20b6..203f97956 100644 --- a/src/systems/filecoin_mining/storage_mining/storage_mining_subsystem.go +++ b/src/systems/filecoin_mining/storage_mining/storage_mining_subsystem.go @@ -49,8 +49,8 @@ func (sms *StorageMiningSubsystem_I) OnNewRound() { } func (sms *StorageMiningSubsystem_I) tryLeaderElection() { - T1 := storagePowerConsensus.GetTicketProductionSeed(sms.CurrentChain) - TK := storagePowerConsensus.GetElectionProofSeed(sms.CurrentChain) + T1 := storagePowerConsensus.GetTicketProductionSeed(sms.CurrentChain, sms.Blockchain.LatestEpoch()) + TK := storagePowerConsensus.GetElectionProofSeed(sms.CurrentChain, sms.Blockchain.LatestEpoch()) for _, worker := range sms.workers { newTicket := PrepareNewTicket(worker.VRFKeyPair, T1)