Merge pull request #8606 from filecoin-project/asr/fix-drand-round

Fix: drand: calculation of round from Filecoin epochs
This commit is contained in:
Aayush Rajasekaran 2022-05-25 15:36:53 -04:00 committed by GitHub
commit 06279b5f1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 135 additions and 115 deletions

View File

@ -171,14 +171,6 @@ type FullNode interface {
// ChainBlockstoreInfo returns some basic information about the blockstore
ChainBlockstoreInfo(context.Context) (map[string]interface{}, error) //perm:read
// MethodGroup: Beacon
// The Beacon method group contains methods for interacting with the random beacon (DRAND)
// BeaconGetEntry returns the beacon entry for the given filecoin epoch. If
// the entry has not yet been produced, the call will block until the entry
// becomes available
BeaconGetEntry(ctx context.Context, epoch abi.ChainEpoch) (*types.BeaconEntry, error) //perm:read
// GasEstimateFeeCap estimates gas fee cap
GasEstimateFeeCap(context.Context, *types.Message, int64, types.TipSetKey) (types.BigInt, error) //perm:read
@ -591,6 +583,11 @@ type FullNode interface {
// StateGetRandomnessFromBeacon is used to sample the beacon for randomness.
StateGetRandomnessFromBeacon(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) //perm:read
// StateGetBeaconEntry returns the beacon entry for the given filecoin epoch. If
// the entry has not yet been produced, the call will block until the entry
// becomes available
StateGetBeaconEntry(ctx context.Context, epoch abi.ChainEpoch) (*types.BeaconEntry, error) //perm:read
// MethodGroup: Msig
// The Msig methods are used to interact with multisig wallets on the
// filecoin network

View File

@ -91,21 +91,6 @@ func (mr *MockFullNodeMockRecorder) AuthVerify(arg0, arg1 interface{}) *gomock.C
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthVerify", reflect.TypeOf((*MockFullNode)(nil).AuthVerify), arg0, arg1)
}
// BeaconGetEntry mocks base method.
func (m *MockFullNode) BeaconGetEntry(arg0 context.Context, arg1 abi.ChainEpoch) (*types.BeaconEntry, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BeaconGetEntry", arg0, arg1)
ret0, _ := ret[0].(*types.BeaconEntry)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BeaconGetEntry indicates an expected call of BeaconGetEntry.
func (mr *MockFullNodeMockRecorder) BeaconGetEntry(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeaconGetEntry", reflect.TypeOf((*MockFullNode)(nil).BeaconGetEntry), arg0, arg1)
}
// ChainBlockstoreInfo mocks base method.
func (m *MockFullNode) ChainBlockstoreInfo(arg0 context.Context) (map[string]interface{}, error) {
m.ctrl.T.Helper()
@ -2406,6 +2391,21 @@ func (mr *MockFullNodeMockRecorder) StateGetActor(arg0, arg1, arg2 interface{})
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetActor", reflect.TypeOf((*MockFullNode)(nil).StateGetActor), arg0, arg1, arg2)
}
// StateGetBeaconEntry mocks base method.
func (m *MockFullNode) StateGetBeaconEntry(arg0 context.Context, arg1 abi.ChainEpoch) (*types.BeaconEntry, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StateGetBeaconEntry", arg0, arg1)
ret0, _ := ret[0].(*types.BeaconEntry)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// StateGetBeaconEntry indicates an expected call of StateGetBeaconEntry.
func (mr *MockFullNodeMockRecorder) StateGetBeaconEntry(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetBeaconEntry", reflect.TypeOf((*MockFullNode)(nil).StateGetBeaconEntry), arg0, arg1)
}
// StateGetRandomnessFromBeacon mocks base method.
func (m *MockFullNode) StateGetRandomnessFromBeacon(arg0 context.Context, arg1 crypto.DomainSeparationTag, arg2 abi.ChainEpoch, arg3 []byte, arg4 types.TipSetKey) (abi.Randomness, error) {
m.ctrl.T.Helper()

View File

@ -103,8 +103,6 @@ type FullNodeStruct struct {
NetStruct
Internal struct {
BeaconGetEntry func(p0 context.Context, p1 abi.ChainEpoch) (*types.BeaconEntry, error) `perm:"read"`
ChainBlockstoreInfo func(p0 context.Context) (map[string]interface{}, error) `perm:"read"`
ChainCheckBlockstore func(p0 context.Context) error `perm:"admin"`
@ -353,6 +351,8 @@ type FullNodeStruct struct {
StateGetActor func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) `perm:"read"`
StateGetBeaconEntry func(p0 context.Context, p1 abi.ChainEpoch) (*types.BeaconEntry, error) `perm:"read"`
StateGetRandomnessFromBeacon func(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) `perm:"read"`
StateGetRandomnessFromTickets func(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) `perm:"read"`
@ -1088,17 +1088,6 @@ func (s *CommonStub) Version(p0 context.Context) (APIVersion, error) {
return *new(APIVersion), ErrNotSupported
}
func (s *FullNodeStruct) BeaconGetEntry(p0 context.Context, p1 abi.ChainEpoch) (*types.BeaconEntry, error) {
if s.Internal.BeaconGetEntry == nil {
return nil, ErrNotSupported
}
return s.Internal.BeaconGetEntry(p0, p1)
}
func (s *FullNodeStub) BeaconGetEntry(p0 context.Context, p1 abi.ChainEpoch) (*types.BeaconEntry, error) {
return nil, ErrNotSupported
}
func (s *FullNodeStruct) ChainBlockstoreInfo(p0 context.Context) (map[string]interface{}, error) {
if s.Internal.ChainBlockstoreInfo == nil {
return *new(map[string]interface{}), ErrNotSupported
@ -2463,6 +2452,17 @@ func (s *FullNodeStub) StateGetActor(p0 context.Context, p1 address.Address, p2
return nil, ErrNotSupported
}
func (s *FullNodeStruct) StateGetBeaconEntry(p0 context.Context, p1 abi.ChainEpoch) (*types.BeaconEntry, error) {
if s.Internal.StateGetBeaconEntry == nil {
return nil, ErrNotSupported
}
return s.Internal.StateGetBeaconEntry(p0, p1)
}
func (s *FullNodeStub) StateGetBeaconEntry(p0 context.Context, p1 abi.ChainEpoch) (*types.BeaconEntry, error) {
return nil, ErrNotSupported
}
func (s *FullNodeStruct) StateGetRandomnessFromBeacon(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) {
if s.Internal.StateGetRandomnessFromBeacon == nil {
return *new(abi.Randomness), ErrNotSupported

View File

@ -352,4 +352,8 @@ func (w *WrapperV1Full) ClientQueryAsk(ctx context.Context, p peer.ID, miner add
return a.Response, nil
}
func (w *WrapperV1Full) BeaconGetEntry(ctx context.Context, epoch abi.ChainEpoch) (*types.BeaconEntry, error) {
return w.StateGetBeaconEntry(ctx, epoch)
}
var _ FullNode = &WrapperV1Full{}

Binary file not shown.

View File

@ -3,6 +3,8 @@ package beacon
import (
"context"
"github.com/filecoin-project/go-state-types/network"
"github.com/filecoin-project/go-state-types/abi"
logging "github.com/ipfs/go-log/v2"
"golang.org/x/xerrors"
@ -42,10 +44,10 @@ type BeaconPoint struct {
type RandomBeacon interface {
Entry(context.Context, uint64) <-chan Response
VerifyEntry(types.BeaconEntry, types.BeaconEntry) error
MaxBeaconRoundForEpoch(abi.ChainEpoch) uint64
MaxBeaconRoundForEpoch(network.Version, abi.ChainEpoch) uint64
}
func ValidateBlockValues(bSchedule Schedule, h *types.BlockHeader, parentEpoch abi.ChainEpoch,
func ValidateBlockValues(bSchedule Schedule, nv network.Version, h *types.BlockHeader, parentEpoch abi.ChainEpoch,
prevEntry types.BeaconEntry) error {
{
parentBeacon := bSchedule.BeaconForEpoch(parentEpoch)
@ -65,7 +67,7 @@ func ValidateBlockValues(bSchedule Schedule, h *types.BlockHeader, parentEpoch a
// TODO: fork logic
b := bSchedule.BeaconForEpoch(h.Height)
maxRound := b.MaxBeaconRoundForEpoch(h.Height)
maxRound := b.MaxBeaconRoundForEpoch(nv, h.Height)
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))
@ -92,13 +94,13 @@ func ValidateBlockValues(bSchedule Schedule, h *types.BlockHeader, parentEpoch a
return nil
}
func BeaconEntriesForBlock(ctx context.Context, bSchedule Schedule, epoch abi.ChainEpoch, parentEpoch abi.ChainEpoch, prev types.BeaconEntry) ([]types.BeaconEntry, error) {
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(epoch)
round := currBeacon.MaxBeaconRoundForEpoch(nv, epoch)
out := make([]types.BeaconEntry, 2)
rch := currBeacon.Entry(ctx, round-1)
res := <-rch
@ -120,7 +122,7 @@ func BeaconEntriesForBlock(ctx context.Context, bSchedule Schedule, epoch abi.Ch
start := build.Clock.Now()
maxRound := beacon.MaxBeaconRoundForEpoch(epoch)
maxRound := beacon.MaxBeaconRoundForEpoch(nv, epoch)
if maxRound == prev.Round {
return nil, nil
}

View File

@ -5,6 +5,8 @@ import (
"context"
"time"
"github.com/filecoin-project/go-state-types/network"
dchain "github.com/drand/drand/chain"
dclient "github.com/drand/drand/client"
hclient "github.com/drand/drand/client/http"
@ -201,11 +203,32 @@ func (db *DrandBeacon) VerifyEntry(curr types.BeaconEntry, prev types.BeaconEntr
return err
}
func (db *DrandBeacon) MaxBeaconRoundForEpoch(filEpoch abi.ChainEpoch) uint64 {
func (db *DrandBeacon) MaxBeaconRoundForEpoch(nv network.Version, filEpoch abi.ChainEpoch) uint64 {
// TODO: sometimes the genesis time for filecoin is zero and this goes negative
latestTs := ((uint64(filEpoch) * db.filRoundTime) + db.filGenTime) - db.filRoundTime
if nv <= network.Version15 {
return db.maxBeaconRoundV1(latestTs)
}
return db.maxBeaconRoundV2(latestTs)
}
func (db *DrandBeacon) maxBeaconRoundV1(latestTs uint64) uint64 {
dround := (latestTs - db.drandGenTime) / uint64(db.interval.Seconds())
return dround
}
func (db *DrandBeacon) maxBeaconRoundV2(latestTs uint64) uint64 {
if latestTs < db.drandGenTime {
return 1
}
fromGenesis := latestTs - db.drandGenTime
// 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.
return fromGenesis/uint64(db.interval.Seconds()) + 1
}
var _ beacon.RandomBeacon = (*DrandBeacon)(nil)

View File

@ -6,6 +6,8 @@ import (
"os"
"testing"
"github.com/filecoin-project/go-state-types/network"
dchain "github.com/drand/drand/chain"
hclient "github.com/drand/drand/client/http"
"github.com/stretchr/testify/assert"
@ -25,3 +27,12 @@ func TestPrintGroupInfo(t *testing.T) {
err = chain.ToJSON(os.Stdout)
assert.NoError(t, err)
}
func TestMaxBeaconRoundForEpoch(t *testing.T) {
todayTs := uint64(1652222222)
db, err := NewDrandBeacon(todayTs, build.BlockDelaySecs, nil, build.DrandConfigs[build.DrandDevnet])
assert.NoError(t, err)
mbr15 := db.MaxBeaconRoundForEpoch(network.Version15, 100)
mbr16 := db.MaxBeaconRoundForEpoch(network.Version16, 100)
assert.Equal(t, mbr15+1, mbr16)
}

View File

@ -6,6 +6,8 @@ import (
"encoding/binary"
"time"
"github.com/filecoin-project/go-state-types/network"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/chain/types"
"github.com/minio/blake2b-simd"
@ -53,7 +55,7 @@ func (mb *mockBeacon) VerifyEntry(from types.BeaconEntry, to types.BeaconEntry)
return nil
}
func (mb *mockBeacon) MaxBeaconRoundForEpoch(epoch abi.ChainEpoch) uint64 {
func (mb *mockBeacon) MaxBeaconRoundForEpoch(nv network.Version, epoch abi.ChainEpoch) uint64 {
// offset for better testing
return uint64(epoch + 100)
}

View File

@ -264,7 +264,8 @@ func (filec *FilecoinEC) ValidateBlock(ctx context.Context, b *types.FullBlock)
return nil
}
if err := beacon.ValidateBlockValues(filec.beacon, h, baseTs.Height(), *prevBeacon); err != nil {
nv := filec.sm.GetNetworkVersion(ctx, h.Height)
if err := beacon.ValidateBlockValues(filec.beacon, nv, h, baseTs.Height(), *prevBeacon); err != nil {
return xerrors.Errorf("failed to validate blocks random beacon values: %w", err)
}
return nil
@ -488,7 +489,7 @@ func (filec *FilecoinEC) checkBlockMessages(ctx context.Context, b *types.FullBl
// Phase 2: (Partial) semantic validation:
// the sender exists and is an account actor, and the nonces make sense
var sender address.Address
if filec.sm.GetNetworkVersion(ctx, b.Header.Height) >= network.Version13 {
if nv >= network.Version13 {
sender, err = st.LookupID(m.From)
if err != nil {
return xerrors.Errorf("failed to lookup sender %s: %w", m.From, err)

View File

@ -198,7 +198,9 @@ func (sr *stateRand) extractBeaconEntryForEpoch(ctx context.Context, filecoinEpo
return nil, err
}
round := sr.beacon.BeaconForEpoch(filecoinEpoch).MaxBeaconRoundForEpoch(filecoinEpoch)
nv := sr.networkVersionGetter(ctx, filecoinEpoch)
round := sr.beacon.BeaconForEpoch(filecoinEpoch).MaxBeaconRoundForEpoch(nv, filecoinEpoch)
for i := 0; i < 20; i++ {
cbe := randTs.Blocks()[0].BeaconEntries

View File

@ -317,7 +317,7 @@ func MinerGetBaseInfo(ctx context.Context, sm *StateManager, bcs beacon.Schedule
prev = &types.BeaconEntry{}
}
entries, err := beacon.BeaconEntriesForBlock(ctx, bcs, round, ts.Height(), *prev)
entries, err := beacon.BeaconEntriesForBlock(ctx, bcs, sm.GetNetworkVersion(ctx, round), round, ts.Height(), *prev)
if err != nil {
return nil, err
}

View File

@ -8,8 +8,6 @@
* [Auth](#Auth)
* [AuthNew](#AuthNew)
* [AuthVerify](#AuthVerify)
* [Beacon](#Beacon)
* [BeaconGetEntry](#BeaconGetEntry)
* [Chain](#Chain)
* [ChainBlockstoreInfo](#ChainBlockstoreInfo)
* [ChainCheckBlockstore](#ChainCheckBlockstore)
@ -175,6 +173,7 @@
* [StateDecodeParams](#StateDecodeParams)
* [StateEncodeParams](#StateEncodeParams)
* [StateGetActor](#StateGetActor)
* [StateGetBeaconEntry](#StateGetBeaconEntry)
* [StateGetRandomnessFromBeacon](#StateGetRandomnessFromBeacon)
* [StateGetRandomnessFromTickets](#StateGetRandomnessFromTickets)
* [StateListActors](#StateListActors)
@ -340,33 +339,6 @@ Response:
]
```
## Beacon
The Beacon method group contains methods for interacting with the random beacon (DRAND)
### BeaconGetEntry
BeaconGetEntry returns the beacon entry for the given filecoin epoch. If
the entry has not yet been produced, the call will block until the entry
becomes available
Perms: read
Inputs:
```json
[
10101
]
```
Response:
```json
{
"Round": 42,
"Data": "Ynl0ZSBhcnJheQ=="
}
```
## Chain
The Chain method group contains methods for interacting with the
blockchain, but that do not require any form of state computation.
@ -5641,6 +5613,29 @@ Response:
}
```
### StateGetBeaconEntry
StateGetBeaconEntry returns the beacon entry for the given filecoin epoch. If
the entry has not yet been produced, the call will block until the entry
becomes available
Perms: read
Inputs:
```json
[
10101
]
```
Response:
```json
{
"Round": 42,
"Data": "Ynl0ZSBhcnJheQ=="
}
```
### StateGetRandomnessFromBeacon
StateGetRandomnessFromBeacon is used to sample the beacon for randomness.

View File

@ -260,7 +260,7 @@ minerLoop:
}
// just wait for the beacon entry to become available before we select our final mining base
_, err = m.api.BeaconGetEntry(ctx, prebase.TipSet.Height()+prebase.NullRounds+1)
_, err = m.api.StateGetBeaconEntry(ctx, prebase.TipSet.Height()+prebase.NullRounds+1)
if err != nil {
log.Errorf("failed getting beacon entry: %s", err)
if !m.niceSleep(time.Second) {

View File

@ -35,7 +35,6 @@ type FullNodeAPI struct {
full.MsigAPI
full.WalletAPI
full.SyncAPI
full.BeaconAPI
DS dtypes.MetadataDS
NetworkName dtypes.NetworkName

View File

@ -1,36 +0,0 @@
package full
import (
"context"
"fmt"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/chain/beacon"
"github.com/filecoin-project/lotus/chain/types"
"go.uber.org/fx"
)
type BeaconAPI struct {
fx.In
Beacon beacon.Schedule
}
func (a *BeaconAPI) BeaconGetEntry(ctx context.Context, epoch abi.ChainEpoch) (*types.BeaconEntry, error) {
b := a.Beacon.BeaconForEpoch(epoch)
rr := b.MaxBeaconRoundForEpoch(epoch)
e := b.Entry(ctx, rr)
select {
case be, ok := <-e:
if !ok {
return nil, fmt.Errorf("beacon get returned no value")
}
if be.Err != nil {
return nil, be.Err
}
return &be.Entry, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}

View File

@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"strconv"
"github.com/libp2p/go-libp2p-core/peer"
@ -1457,3 +1458,22 @@ func (a *StateAPI) StateGetRandomnessFromBeacon(ctx context.Context, personaliza
return a.StateManager.GetRandomnessFromBeacon(ctx, personalization, randEpoch, entropy, tsk)
}
func (a *StateAPI) StateGetBeaconEntry(ctx context.Context, epoch abi.ChainEpoch) (*types.BeaconEntry, error) {
b := a.Beacon.BeaconForEpoch(epoch)
rr := b.MaxBeaconRoundForEpoch(a.StateManager.GetNetworkVersion(ctx, epoch), epoch)
e := b.Entry(ctx, rr)
select {
case be, ok := <-e:
if !ok {
return nil, fmt.Errorf("beacon get returned no value")
}
if be.Err != nil {
return nil, be.Err
}
return &be.Entry, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}