Skip to content

Commit

Permalink
ticket sampling coded.
Browse files Browse the repository at this point in the history
  • Loading branch information
sternhenri committed Oct 11, 2019
1 parent 6ce8ac8 commit fb79a8d
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 47 deletions.
4 changes: 4 additions & 0 deletions src/algorithms/expected_consensus.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion src/systems/filecoin_blockchain/base_blockchain.id
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ type ElectionProof struct {
}

type ChainWeight UVarint
type ChainHeight UVarint
type Epoch UVarint
type ElectionNonce UVarint

Expand Down
72 changes: 50 additions & 22 deletions src/systems/filecoin_blockchain/blockchain/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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()
}
16 changes: 7 additions & 9 deletions src/systems/filecoin_blockchain/blockchain/block.id
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,6 +20,9 @@ type Block struct {

// SerializeSigned() []byte
// ComputeUnsignedFingerprint() []

TipsetAtEpoch(epoch Epoch) Tipset
TicketAtEpoch(epoch Epoch) base_blockchain.Ticket
}

type SignedBlock struct {
Expand All @@ -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)
}
2 changes: 2 additions & 0 deletions src/systems/filecoin_blockchain/blockchain/block_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
// }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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]`.


Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
Expand All @@ -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)

Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit fb79a8d

Please sign in to comment.