Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: StateGetBeaconEntry uses chain beacons for historical epochs #6408

Merged
merged 1 commit into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions app/submodule/chain/chaininfo_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,21 @@ func (cia *chainInfoAPI) VerifyEntry(parent, child *types.BeaconEntry, height ab
// the entry has not yet been produced, the call will block until the entry
// becomes available
func (cia *chainInfoAPI) StateGetBeaconEntry(ctx context.Context, epoch abi.ChainEpoch) (*types.BeaconEntry, error) {
ts := cia.chain.ChainReader.GetHead()
if epoch <= ts.Height() {
if epoch < 0 {
epoch = 0
}
// get the beacon entry off the chain
ts, err := cia.chain.ChainReader.GetTipSet(ctx, types.EmptyTSK)
if err != nil {
return nil, err
}
r := chain.NewChainRandomnessSource(cia.chain.ChainReader, ts.Key(), cia.chain.Drand, cia.chain.Fork.GetNetworkVersion)
return r.GetBeaconEntry(ctx, epoch)
}

// else we're asking for the future, get it from drand and block until it arrives
b := cia.chain.Drand.BeaconForEpoch(epoch)
nv := cia.chain.Fork.GetNetworkVersion(ctx, epoch)
rr := b.MaxBeaconRoundForEpoch(nv, epoch)
Expand Down
77 changes: 62 additions & 15 deletions pkg/beacon/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"encoding/binary"
"fmt"
"sync"
"time"

"github.com/filecoin-project/go-state-types/abi"
Expand All @@ -13,14 +14,22 @@ import (
"github.com/minio/blake2b-simd"
)

// Mock beacon assumes that filecoin rounds are 1:1 mapped with the beacon rounds
type mockBeacon struct {
interval time.Duration
// MockBeacon assumes that filecoin rounds are 1:1 mapped with the beacon rounds
type MockBeacon struct {
interval time.Duration
maxIndex int
waitingEntry int
lk sync.Mutex
cond *sync.Cond
}

func NewMockBeacon(interval time.Duration) RandomBeacon {
mb := &mockBeacon{interval: interval}
func (mb *MockBeacon) IsChained() bool {
return true
}

func NewMockBeacon(interval time.Duration) RandomBeacon {
mb := &MockBeacon{interval: interval, maxIndex: -1}
mb.cond = sync.NewCond(&mb.lk)
return mb
}

Expand All @@ -31,15 +40,35 @@ func NewMockSchedule(interval time.Duration) Schedule {
}}
}

func (mb *mockBeacon) IsChained() bool {
return true
// SetMaxIndex sets the maximum index that the beacon will return, and optionally blocks until all
// waiting requests are satisfied. If maxIndex is -1, the beacon will return entries indefinitely.
func (mb *MockBeacon) SetMaxIndex(maxIndex int, blockTillNoneWaiting bool) {
mb.lk.Lock()
defer mb.lk.Unlock()
mb.maxIndex = maxIndex
mb.cond.Broadcast()
if !blockTillNoneWaiting {
return
}

for mb.waitingEntry > 0 {
mb.cond.Wait()
}
}

// WaitingOnEntryCount returns the number of requests that are currently waiting for an entry. Where
// maxIndex has not been set, this will always return 0 as beacon entries are generated on demand.
func (mb *MockBeacon) WaitingOnEntryCount() int {
mb.lk.Lock()
defer mb.lk.Unlock()
return mb.waitingEntry
}

func (mb *mockBeacon) RoundTime() time.Duration {
func (mb *MockBeacon) RoundTime() time.Duration {
return mb.interval
}

func (mb *mockBeacon) entryForIndex(index uint64) types.BeaconEntry {
func (mb *MockBeacon) entryForIndex(index uint64) types.BeaconEntry {
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, index)
rval := blake2b.Sum256(buf)
Expand All @@ -49,14 +78,32 @@ func (mb *mockBeacon) entryForIndex(index uint64) types.BeaconEntry {
}
}

func (mb *mockBeacon) Entry(ctx context.Context, index uint64) <-chan Response {
e := mb.entryForIndex(index)
func (mb *MockBeacon) Entry(ctx context.Context, index uint64) <-chan Response {
out := make(chan Response, 1)
out <- Response{Entry: e}

mb.lk.Lock()
defer mb.lk.Unlock()

if mb.maxIndex >= 0 && index > uint64(mb.maxIndex) {
mb.waitingEntry++
go func() {
mb.lk.Lock()
defer mb.lk.Unlock()
for index > uint64(mb.maxIndex) {
mb.cond.Wait()
}
out <- Response{Entry: mb.entryForIndex(index)}
mb.waitingEntry--
mb.cond.Broadcast()
}()
} else {
out <- Response{Entry: mb.entryForIndex(index)}
}

return out
}

func (mb *mockBeacon) VerifyEntry(from types.BeaconEntry, _prevEntrySig []byte) 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 All @@ -65,9 +112,9 @@ func (mb *mockBeacon) VerifyEntry(from types.BeaconEntry, _prevEntrySig []byte)
return nil
}

func (mb *mockBeacon) MaxBeaconRoundForEpoch(nv network.Version, epoch abi.ChainEpoch) uint64 {
func (mb *MockBeacon) MaxBeaconRoundForEpoch(nv network.Version, epoch abi.ChainEpoch) uint64 {
// offset for better testing
return uint64(epoch + 100)
}

var _ RandomBeacon = (*mockBeacon)(nil)
var _ RandomBeacon = (*MockBeacon)(nil)
101 changes: 67 additions & 34 deletions pkg/chain/randomness.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"encoding/binary"
"fmt"
"math/rand"
"os"

"github.com/filecoin-project/venus/pkg/beacon"
"github.com/filecoin-project/venus/pkg/vm"
Expand All @@ -32,7 +33,7 @@
return &GenesisRandomnessSource{vrf: vrf}
}

func (g *GenesisRandomnessSource) ChainGetRandomnessFromBeacon(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) {

Check failure on line 36 in pkg/chain/randomness.go

View workflow job for this annotation

GitHub Actions / check

ST1016: methods on the same type should have the same receiver name (seen 1x "r", 4x "g") (stylecheck)
out := make([]byte, 32)
_, _ = rand.New(rand.NewSource(int64(randEpoch))).Read(out) //nolint
return out, nil
Expand All @@ -54,6 +55,11 @@
_, _ = rand.New(rand.NewSource(int64(randEpoch))).Read(out) //nolint
return *(*[32]byte)(out), nil
}
func (r *GenesisRandomnessSource) GetBeaconEntry(_ context.Context, randEpoch abi.ChainEpoch) (*types.BeaconEntry, error) {
out := make([]byte, 32)
_, _ = rand.New(rand.NewSource(int64(randEpoch))).Read(out) //nolint
return &types.BeaconEntry{Round: 10, Data: out}, nil
}

// Computes a random seed from raw ticket bytes.
// A randomness seed is the VRF digest of the minimum ticket of the tipset at or before the requested epoch
Expand Down Expand Up @@ -154,8 +160,8 @@
}

// network v13 and on
func (c *ChainRandomnessSource) GetChainRandomnessV2(ctx context.Context, round abi.ChainEpoch) ([32]byte, error) {
ticket, err := c.getChainRandomness(ctx, round, false)
func (c *ChainRandomnessSource) GetChainRandomnessV2(ctx context.Context, round abi.ChainEpoch, lookback bool) ([32]byte, error) {
ticket, err := c.getChainRandomness(ctx, round, lookback)
if err != nil {
return [32]byte{}, err
}
Expand All @@ -167,58 +173,46 @@
nv := c.networkVersionGetter(ctx, filecoinEpoch)

if nv >= network.Version13 {
return c.GetChainRandomnessV2(ctx, filecoinEpoch)
return c.GetChainRandomnessV2(ctx, filecoinEpoch, false)
}

return c.GetChainRandomnessV2(ctx, filecoinEpoch)
return c.GetChainRandomnessV2(ctx, filecoinEpoch, true)
}

// network v0-12
func (c *ChainRandomnessSource) GetBeaconRandomnessV1(ctx context.Context, round abi.ChainEpoch) ([32]byte, error) {
randTS, err := c.GetBeaconRandomnessTipset(ctx, round, true)
func (c *ChainRandomnessSource) GetBeaconRandomness(ctx context.Context, filecoinEpoch abi.ChainEpoch) ([32]byte, error) {
be, err := c.GetBeaconEntry(ctx, filecoinEpoch)
if err != nil {
return [32]byte{}, err
}
return blake2b.Sum256(be.Data), nil
}

be, err := FindLatestDRAND(ctx, randTS, c.reader)
// network v0-12
func (c *ChainRandomnessSource) getBeaconEntryV1(ctx context.Context, round abi.ChainEpoch) (*types.BeaconEntry, error) {
randTS, err := c.GetBeaconRandomnessTipset(ctx, round, true)
if err != nil {
return [32]byte{}, err
return nil, err
}

return blake2b.Sum256(be.Data), nil
return c.getLatestBeaconEntry(ctx, randTS)
}

// network v13
func (c *ChainRandomnessSource) GetBeaconRandomnessV2(ctx context.Context, round abi.ChainEpoch) ([32]byte, error) {
func (c *ChainRandomnessSource) getBeaconEntryV2(ctx context.Context, round abi.ChainEpoch) (*types.BeaconEntry, error) {
randTS, err := c.GetBeaconRandomnessTipset(ctx, round, false)
if err != nil {
return [32]byte{}, err
}

be, err := FindLatestDRAND(ctx, randTS, c.reader)
if err != nil {
return [32]byte{}, err
return nil, err
}

return blake2b.Sum256(be.Data), nil
return c.getLatestBeaconEntry(ctx, randTS)
}

// network v14 and on
func (c *ChainRandomnessSource) GetBeaconRandomnessV3(ctx context.Context, filecoinEpoch abi.ChainEpoch) ([32]byte, error) {
func (c *ChainRandomnessSource) getBeaconEntryV3(ctx context.Context, filecoinEpoch abi.ChainEpoch) (*types.BeaconEntry, error) {
if filecoinEpoch < 0 {
return c.GetBeaconRandomnessV2(ctx, filecoinEpoch)
}

be, err := c.extractBeaconEntryForEpoch(ctx, filecoinEpoch)
if err != nil {
log.Errorf("failed to get beacon entry as expected: %s", err)
return [32]byte{}, err
return c.getBeaconEntryV2(ctx, filecoinEpoch)
}

return blake2b.Sum256(be.Data), nil
}

func (c *ChainRandomnessSource) extractBeaconEntryForEpoch(ctx context.Context, filecoinEpoch abi.ChainEpoch) (*types.BeaconEntry, error) {
randTS, err := c.GetBeaconRandomnessTipset(ctx, filecoinEpoch, false)
if err != nil {
return nil, err
Expand All @@ -228,6 +222,9 @@

round := c.beacon.BeaconForEpoch(filecoinEpoch).MaxBeaconRoundForEpoch(nv, filecoinEpoch)

// Search back for the beacon entry, in normal operation it should be in randTs but for devnets
// where the blocktime is faster than the beacon period we may need to search back a bit to find
// the beacon entry for the requested round.
for i := 0; i < 20; i++ {
cbe := randTS.Blocks()[0].BeaconEntries
for _, v := range cbe {
Expand All @@ -247,15 +244,51 @@
return nil, fmt.Errorf("didn't find beacon for round %d (epoch %d)", round, filecoinEpoch)
}

func (c *ChainRandomnessSource) GetBeaconRandomness(ctx context.Context, randEpoch abi.ChainEpoch) ([32]byte, error) {
func (c *ChainRandomnessSource) GetBeaconEntry(ctx context.Context, randEpoch abi.ChainEpoch) (*types.BeaconEntry, error) {
rnv := c.networkVersionGetter(ctx, randEpoch)
if rnv >= network.Version14 {
return c.GetBeaconRandomnessV3(ctx, randEpoch)
be, err := c.getBeaconEntryV3(ctx, randEpoch)
if err != nil {
log.Errorf("failed to get beacon entry as expected: %s", err)
}
return be, err
} else if rnv == network.Version13 {
return c.GetBeaconRandomnessV2(ctx, randEpoch)
return c.getBeaconEntryV2(ctx, randEpoch)
}

return c.getBeaconEntryV1(ctx, randEpoch)
}

func (c *ChainRandomnessSource) getLatestBeaconEntry(ctx context.Context, ts *types.TipSet) (*types.BeaconEntry, error) {
cur := ts

// Search for a beacon entry, in normal operation one should be in the requested tipset, but for
// devnets where the blocktime is faster than the beacon period we may need to search back a bit
// to find a tipset with a beacon entry.
for i := 0; i < 20; i++ {
cbe := cur.Blocks()[0].BeaconEntries
if len(cbe) > 0 {
return &cbe[len(cbe)-1], nil
}

if cur.Height() == 0 {
return nil, fmt.Errorf("made it back to genesis block without finding beacon entry")
}

next, err := c.reader.GetTipSet(ctx, cur.Parents())
if err != nil {
return nil, fmt.Errorf("failed to load parents when searching back for latest beacon entry: %w", err)
}
cur = next
}

if os.Getenv("VENUS_IGNORE_DRAND") == "_yes_" {
return &types.BeaconEntry{
Data: []byte{9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9},
}, nil
}

return c.GetBeaconRandomnessV1(ctx, randEpoch)
return nil, fmt.Errorf("found NO beacon entries in the 20 latest tipsets")
}

// BlendEntropy get randomness with chain value. sha256(buf(tag, seed, epoch, entropy))
Expand Down
5 changes: 5 additions & 0 deletions pkg/gen/genesis/miners.go
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,11 @@ func SetupStorageMiners(ctx context.Context,
// TODO: copied from actors test harness, deduplicate or remove from here
type fakeRand struct{}

func (fr *fakeRand) GetBeaconEntry(ctx context.Context, randEpoch abi.ChainEpoch) (*types.BeaconEntry, error) {
r, _ := fr.GetChainRandomness(ctx, randEpoch)
return &types.BeaconEntry{Round: 10, Data: r[:]}, nil
}

func (fr *fakeRand) GetBeaconRandomness(ctx context.Context, randEpoch abi.ChainEpoch) ([32]byte, error) {
out := make([]byte, 32)
_, _ = rand.New(rand.NewSource(int64(randEpoch * 1000))).Read(out) //nolint
Expand Down
1 change: 1 addition & 0 deletions pkg/vm/vmcontext/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ func TipSetGetterForTipset(tsGet func(context.Context, *types.TipSet, abi.ChainE
// ChainRandomness define randomness method in filecoin
type HeadChainRandomness interface {
GetChainRandomness(ctx context.Context, round abi.ChainEpoch) ([32]byte, error)
GetBeaconEntry(ctx context.Context, round abi.ChainEpoch) (*types.BeaconEntry, error)
GetBeaconRandomness(ctx context.Context, round abi.ChainEpoch) ([32]byte, error)
}

Expand Down
5 changes: 5 additions & 0 deletions tools/conformance/rand_fixed.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

"github.com/filecoin-project/venus/pkg/vm/vmcontext"
"github.com/filecoin-project/venus/venus-shared/types"

"github.com/filecoin-project/go-state-types/abi"
)
Expand All @@ -27,3 +28,7 @@ func (r *fixedRand) GetBeaconRandomness(ctx context.Context, round abi.ChainEpoc
func (r *fixedRand) GetChainRandomness(ctx context.Context, round abi.ChainEpoch) ([32]byte, error) {
return *(*[32]byte)(fixedBytes), nil // 32 bytes.
}

func (r *fixedRand) GetBeaconEntry(_ context.Context, _ abi.ChainEpoch) (*types.BeaconEntry, error) {
return &types.BeaconEntry{Round: 10, Data: []byte("i_am_random_____i_am_random_____")}, nil
}
23 changes: 23 additions & 0 deletions tools/conformance/rand_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,29 @@ func (r *RecordingRand) GetBeaconRandomness(ctx context.Context, round abi.Chain
return *(*[32]byte)(ret), err
}

func (r *RecordingRand) GetBeaconEntry(ctx context.Context, round abi.ChainEpoch) (*types.BeaconEntry, error) {
r.once.Do(r.loadHead)
ret, err := r.api.StateGetBeaconEntry(ctx, round)
if err != nil {
return nil, err
}

r.reporter.Logf("fetched and recorded beacon randomness for: epoch=%d, result=%x", round, ret)

match := schema.RandomnessMatch{
On: schema.RandomnessRule{
Kind: schema.RandomnessBeacon,
Epoch: int64(round),
},
Return: ret.Data,
}
r.lk.Lock()
r.recorded = append(r.recorded, match)
r.lk.Unlock()

return ret, err
}

func (r *RecordingRand) Recorded() schema.Randomness {
r.lk.Lock()
defer r.lk.Unlock()
Expand Down
Loading
Loading