Skip to content

Commit

Permalink
feat: implement FIP-0063
Browse files Browse the repository at this point in the history
  • Loading branch information
arajasek committed Jan 12, 2024
1 parent 1324822 commit 7ccaa9d
Show file tree
Hide file tree
Showing 14 changed files with 183 additions and 141 deletions.
19 changes: 19 additions & 0 deletions build/drand.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,18 @@ const (
DrandDevnet
DrandLocalnet
DrandIncentinet
DrandQuicknet
)

var DrandConfigs = map[DrandEnum]dtypes.DrandConfig{
DrandMainnet: {
//FUCK: add storswift endpoint
Servers: []string{
"https://api.drand.sh",
"https://api2.drand.sh",
"https://api3.drand.sh",
"https://drand.cloudflare.com",
//"https://api.drand.secureweb3.com:6875", // Storswift
},
Relays: []string{
"/dnsaddr/api.drand.sh/",
Expand All @@ -44,6 +47,22 @@ var DrandConfigs = map[DrandEnum]dtypes.DrandConfig{
},
ChainInfoJSON: `{"public_key":"868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31","period":30,"genesis_time":1595431050,"hash":"8990e7a9aaed2ffed73dbd7092123d6f289930540d7651336225dc172e51b2ce","groupHash":"176f93498eac9ca337150b46d21dd58673ea4e3581185f869672e59fa4cb390a"}`,
},
DrandQuicknet: {
//FUCK: add storswift endpoint
Servers: []string{
"https://api.drand.sh",
"https://api2.drand.sh",
"https://api3.drand.sh",
"https://drand.cloudflare.com",
//"https://api.drand.secureweb3.com:6875", // Storswift
},
Relays: []string{
"/dnsaddr/api.drand.sh/",
"/dnsaddr/api2.drand.sh/",
"/dnsaddr/api3.drand.sh/",
},
ChainInfoJSON: `{"public_key":"83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a","period":3,"genesis_time":1692803367,"hash":"52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971","groupHash":"f477d5c89f21a17c863a7f937c6a6d15859414d2be09cd448d4279af331c5d3e","schemeID":"bls-unchained-g1-rfc9380","metadata":{"beaconID":"quicknet"}}`,
},
DrandTestnet: {
Servers: []string{
"https://pl-eu.testnet.drand.sh",
Expand Down
3 changes: 2 additions & 1 deletion build/params_2k.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ const UpgradeWatermelonFixHeight = -100
const UpgradeWatermelonFix2Height = -101

var DrandSchedule = map[abi.ChainEpoch]DrandEnum{
0: DrandMainnet,
0: DrandMainnet,
UpgradePineappleHeight: DrandQuicknet,
}

var SupportedProofTypes = []abi.RegisteredSealProof{
Expand Down
3 changes: 2 additions & 1 deletion build/params_butterfly.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import (
)

var DrandSchedule = map[abi.ChainEpoch]DrandEnum{
0: DrandMainnet,
0: DrandMainnet,
UpgradePineappleHeight: DrandQuicknet,
}

const GenesisNetworkVersion = network.Version20
Expand Down
3 changes: 2 additions & 1 deletion build/params_calibnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import (
)

var DrandSchedule = map[abi.ChainEpoch]DrandEnum{
0: DrandMainnet,
0: DrandMainnet,
UpgradePineappleHeight: DrandQuicknet,
}

const GenesisNetworkVersion = network.Version0
Expand Down
3 changes: 2 additions & 1 deletion build/params_interop.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ const UpgradeWatermelonFixHeight = -1
const UpgradeWatermelonFix2Height = -2

var DrandSchedule = map[abi.ChainEpoch]DrandEnum{
0: DrandMainnet,
0: DrandMainnet,
UpgradePineappleHeight: DrandQuicknet,
}

var SupportedProofTypes = []abi.RegisteredSealProof{
Expand Down
6 changes: 4 additions & 2 deletions build/params_mainnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import (
builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin"
)

// FUCK: add a block to update to the new network
var DrandSchedule = map[abi.ChainEpoch]DrandEnum{
0: DrandIncentinet,
UpgradeSmokeHeight: DrandMainnet,
0: DrandIncentinet,
UpgradeSmokeHeight: DrandMainnet,
UpgradePineappleHeight: DrandQuicknet,
}

var NetworkBundle = "mainnet"
Expand Down
3 changes: 2 additions & 1 deletion build/params_testground.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ var (
UpgradePineappleHeight abi.ChainEpoch = -26

DrandSchedule = map[abi.ChainEpoch]DrandEnum{
0: DrandMainnet,
0: DrandMainnet,
UpgradePineappleHeight: DrandQuicknet,
}

GenesisNetworkVersion = network.Version0
Expand Down
110 changes: 69 additions & 41 deletions chain/beacon/beacon.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ type Schedule []BeaconPoint
func (bs Schedule) BeaconForEpoch(e abi.ChainEpoch) RandomBeacon {
for i := len(bs) - 1; i >= 0; i-- {
bp := bs[i]
if e >= bp.Start {
// The new network version kicks in at one _after_ the upgrade height itself,
// so we switch to the new beacon when _strictly_ greater than bp.Start
if e > bp.Start {
return bp.Beacon
}
}
Expand All @@ -43,31 +45,31 @@ type BeaconPoint struct {
// been posted on chain.
type RandomBeacon interface {
Entry(context.Context, uint64) <-chan Response
VerifyEntry(types.BeaconEntry, types.BeaconEntry) error
VerifyEntry(entry types.BeaconEntry, prevEntrySig []byte) error
MaxBeaconRoundForEpoch(network.Version, abi.ChainEpoch) uint64
}

func ValidateBlockValues(bSchedule Schedule, nv network.Version, h *types.BlockHeader, parentEpoch abi.ChainEpoch,
prevEntry types.BeaconEntry) error {
{
parentBeacon := bSchedule.BeaconForEpoch(parentEpoch)
currBeacon := bSchedule.BeaconForEpoch(h.Height)
if parentBeacon != currBeacon {
if len(h.BeaconEntries) != 2 {
return xerrors.Errorf("expected two beacon entries at beacon fork, got %d", len(h.BeaconEntries))
}
err := currBeacon.VerifyEntry(h.BeaconEntries[1], h.BeaconEntries[0])
if err != nil {
return xerrors.Errorf("beacon at fork point invalid: (%v, %v): %w",
h.BeaconEntries[1], h.BeaconEntries[0], err)
}
return nil
}
}
//{
// parentBeacon := bSchedule.BeaconForEpoch(parentEpoch)
// currBeacon := bSchedule.BeaconForEpoch(h.Height)
// if parentBeacon != currBeacon {
// if len(h.BeaconEntries) != 2 {
// return xerrors.Errorf("expected two beacon entries at beacon fork, got %d", len(h.BeaconEntries))
// }
// err := currBeacon.VerifyEntry(h.BeaconEntries[1], h.BeaconEntries[0])
// if err != nil {
// return xerrors.Errorf("beacon at fork point invalid: (%v, %v): %w",
// h.BeaconEntries[1], h.BeaconEntries[0], err)
// }
// return nil
// }
//}

// TODO: fork logic
b := bSchedule.BeaconForEpoch(h.Height)
maxRound := b.MaxBeaconRoundForEpoch(nv, h.Height)
// We don't expect to ever actually meet this condition
if maxRound == prevEntry.Round {
if len(h.BeaconEntries) != 0 {
return xerrors.Errorf("expected not to have any beacon entries in this block, got %d", len(h.BeaconEntries))
Expand All @@ -79,13 +81,23 @@ func ValidateBlockValues(bSchedule Schedule, nv network.Version, h *types.BlockH
return xerrors.Errorf("expected to have beacon entries in this block, but didn't find any")
}

if nv >= network.Version22 && len(h.BeaconEntries) != 1 {
return xerrors.Errorf("exactly one beacon entry expected for network version %d, got %d entries", nv, len(h.BeaconEntries))
}

if nv < network.Version22 && prevEntry.Round == 0 {
// This basically means that the drand entry of the first non-genesis tipset isn't verified IF we are starting on Drand mainnet (the "chained" drand)
// Networks that start on drand quicknet, or other unchained randomness sources, will still verify it
return nil
}

last := h.BeaconEntries[len(h.BeaconEntries)-1]
if last.Round != maxRound {
return xerrors.Errorf("expected final beacon entry in block to be at round %d, got %d", maxRound, last.Round)
}

for i, e := range h.BeaconEntries {
if err := b.VerifyEntry(e, prevEntry); err != nil {
if err := b.VerifyEntry(e, prevEntry.Data); err != nil {
return xerrors.Errorf("beacon entry %d (%d - %x (%d)) was invalid: %w", i, e.Round, e.Data, len(e.Data), err)
}
prevEntry = e
Expand All @@ -95,34 +107,35 @@ func ValidateBlockValues(bSchedule Schedule, nv network.Version, h *types.BlockH
}

func BeaconEntriesForBlock(ctx context.Context, bSchedule Schedule, nv network.Version, epoch abi.ChainEpoch, parentEpoch abi.ChainEpoch, prev types.BeaconEntry) ([]types.BeaconEntry, error) {
{
parentBeacon := bSchedule.BeaconForEpoch(parentEpoch)
currBeacon := bSchedule.BeaconForEpoch(epoch)
if parentBeacon != currBeacon {
// Fork logic
round := currBeacon.MaxBeaconRoundForEpoch(nv, epoch)
out := make([]types.BeaconEntry, 2)
rch := currBeacon.Entry(ctx, round-1)
res := <-rch
if res.Err != nil {
return nil, xerrors.Errorf("getting entry %d returned error: %w", round-1, res.Err)
}
out[0] = res.Entry
rch = currBeacon.Entry(ctx, round)
res = <-rch
if res.Err != nil {
return nil, xerrors.Errorf("getting entry %d returned error: %w", round, res.Err)
}
out[1] = res.Entry
return out, nil
}
}
//{
// parentBeacon := bSchedule.BeaconForEpoch(parentEpoch)
// currBeacon := bSchedule.BeaconForEpoch(epoch)
// if parentBeacon != currBeacon {
// // Fork logic
// round := currBeacon.MaxBeaconRoundForEpoch(nv, epoch)
// out := make([]types.BeaconEntry, 2)
// rch := currBeacon.Entry(ctx, prev.Round)
// res := <-rch
// if res.Err != nil {
// return nil, xerrors.Errorf("getting entry %d returned error: %w", round-1, res.Err)
// }
// out[0] = res.Entry
// rch = currBeacon.Entry(ctx, round)
// res = <-rch
// if res.Err != nil {
// return nil, xerrors.Errorf("getting entry %d returned error: %w", round, res.Err)
// }
// out[1] = res.Entry
// return out, nil
// }
//}

beacon := bSchedule.BeaconForEpoch(epoch)

start := build.Clock.Now()

maxRound := beacon.MaxBeaconRoundForEpoch(nv, epoch)
// We don't expect this to ever be the case
if maxRound == prev.Round {
return nil, nil
}
Expand All @@ -132,6 +145,21 @@ func BeaconEntriesForBlock(ctx context.Context, bSchedule Schedule, nv network.V
prev.Round = maxRound - 1
}

// We only ever need one entry after nv22 (FIP-0063)
if nv >= network.Version22 {
rch := beacon.Entry(ctx, maxRound)
select {
case resp := <-rch:
if resp.Err != nil {
return nil, xerrors.Errorf("beacon entry request returned error: %w", resp.Err)
}

return []types.BeaconEntry{resp.Entry}, nil
case <-ctx.Done():
return nil, xerrors.Errorf("context timed out waiting on beacon entry to come back for epoch %d: %w", epoch, ctx.Err())
}
}

cur := maxRound
var out []types.BeaconEntry
for cur > prev.Round {
Expand Down
49 changes: 29 additions & 20 deletions chain/beacon/drand/drand.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
dchain "github.com/drand/drand/chain"
dclient "github.com/drand/drand/client"
hclient "github.com/drand/drand/client/http"
"github.com/drand/drand/common/scheme"
dcrypto "github.com/drand/drand/crypto"
dlog "github.com/drand/drand/log"
gclient "github.com/drand/drand/lp2p/client"
"github.com/drand/kyber"
Expand Down Expand Up @@ -47,6 +47,7 @@ type DrandBeacon struct {
drandGenTime uint64
filGenTime uint64
filRoundTime uint64
scheme *dcrypto.Scheme

localCache *lru.Cache[uint64, *types.BeaconEntry]
}
Expand All @@ -68,6 +69,10 @@ func (l *logger) Named(s string) dlog.Logger {
return &logger{l.SugaredLogger.Named(s)}
}

func (l *logger) AddCallerSkip(skip int) dlog.Logger {
return l
}

func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub, config dtypes.DrandConfig) (*DrandBeacon, error) {
if genesisTs == 0 {
panic("what are you doing this cant be zero")
Expand Down Expand Up @@ -116,6 +121,11 @@ func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub, config dtypes
localCache: lc,
}

sch, err := dcrypto.GetSchemeByIDWithDefault(drandChain.Scheme)
if err != nil {
return nil, err
}
db.scheme = sch
db.pubkey = drandChain.PublicKey
db.interval = drandChain.Period
db.drandGenTime = uint64(drandChain.GenesisTime)
Expand Down Expand Up @@ -164,33 +174,28 @@ func (db *DrandBeacon) getCachedValue(round uint64) *types.BeaconEntry {
return v
}

func (db *DrandBeacon) VerifyEntry(curr types.BeaconEntry, prev types.BeaconEntry) error {
if prev.Round == 0 {
// TODO handle genesis better
return nil
}

if curr.Round != prev.Round+1 {
return xerrors.Errorf("invalid beacon entry: cur (%d) != prev (%d) + 1", curr.Round, prev.Round)
}

if be := db.getCachedValue(curr.Round); be != nil {
if !bytes.Equal(curr.Data, be.Data) {
func (db *DrandBeacon) VerifyEntry(entry types.BeaconEntry, prevEntrySig []byte) error {
if be := db.getCachedValue(entry.Round); be != nil {
if !bytes.Equal(entry.Data, be.Data) {
return xerrors.New("invalid beacon value, does not match cached good value")
}
// return no error if the value is in the cache already
return nil
}
b := &dchain.Beacon{
PreviousSig: prev.Data,
Round: curr.Round,
Signature: curr.Data,
PreviousSig: prevEntrySig,
Round: entry.Round,
Signature: entry.Data,
}
err := dchain.NewVerifier(scheme.GetSchemeFromEnv()).VerifyBeacon(*b, db.pubkey)
if err == nil {
db.cacheValue(curr)

err := db.scheme.VerifyBeacon(b, db.pubkey)
if err != nil {
return xerrors.Errorf("failed to verify beacon: %w", err)
}
return err

db.cacheValue(entry)

return nil
}

func (db *DrandBeacon) MaxBeaconRoundForEpoch(nv network.Version, filEpoch abi.ChainEpoch) uint64 {
Expand Down Expand Up @@ -218,6 +223,10 @@ func (db *DrandBeacon) maxBeaconRoundV2(latestTs uint64) uint64 {
// we take the time from genesis divided by the periods in seconds, that
// gives us the number of periods since genesis. We also add +1 because
// round 1 starts at genesis time.
// TODO: Confirm that we are still fetching the right round for Filecoin
// It should:
// - be available at LEAST 30 (?) seconds before block creation
// - not have been available any more than 30 (???) seconds before block creation
return fromGenesis/uint64(db.interval.Seconds()) + 1
}

Expand Down
2 changes: 1 addition & 1 deletion chain/beacon/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (mb *mockBeacon) Entry(ctx context.Context, index uint64) <-chan Response {
return out
}

func (mb *mockBeacon) VerifyEntry(from types.BeaconEntry, to types.BeaconEntry) error {
func (mb *mockBeacon) VerifyEntry(from types.BeaconEntry, _prevEntrySig []byte) error {
// TODO: cache this, especially for bls
oe := mb.entryForIndex(from.Round)
if !bytes.Equal(from.Data, oe.Data) {
Expand Down
1 change: 1 addition & 0 deletions chain/consensus/filcns/filecoin.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ func (filec *FilecoinEC) ValidateBlock(ctx context.Context, b *types.FullBlock)
return xerrors.Errorf("failed to get lookback tipset for block: %w", err)
}

// TODO: Optimization: We don't need to get latest beacon entry after nv22 and can supply null instead
prevBeacon, err := filec.store.GetLatestBeaconEntry(ctx, baseTs)
if err != nil {
return xerrors.Errorf("failed to get latest beacon entry: %w", err)
Expand Down
Loading

0 comments on commit 7ccaa9d

Please sign in to comment.