package conformance import ( "context" "fmt" "sync" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/test-vectors/schema" "github.com/filecoin-project/lotus/api/v0api" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" ) type RecordingRand struct { reporter Reporter api v0api.FullNode // once guards the loading of the head tipset. // can be removed when https://github.com/filecoin-project/lotus/issues/4223 // is fixed. once sync.Once head types.TipSetKey lk sync.Mutex recorded schema.Randomness } var _ vm.Rand = (*RecordingRand)(nil) // NewRecordingRand returns a vm.Rand implementation that proxies calls to a // full Lotus node via JSON-RPC, and records matching rules and responses so // they can later be embedded in test vectors. func NewRecordingRand(reporter Reporter, api v0api.FullNode) *RecordingRand { return &RecordingRand{reporter: reporter, api: api} } func (r *RecordingRand) loadHead() { head, err := r.api.ChainHead(context.Background()) if err != nil { panic(fmt.Sprintf("could not fetch chain head while fetching randomness: %s", err)) } r.head = head.Key() } func (r *RecordingRand) GetChainRandomnessV2(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { return r.getChainRandomness(ctx, pers, round, entropy) } func (r *RecordingRand) GetChainRandomnessV1(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { return r.getChainRandomness(ctx, pers, round, entropy) } func (r *RecordingRand) getChainRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { r.once.Do(r.loadHead) // FullNode's ChainGetRandomnessFromTickets handles whether we should be looking forward or back ret, err := r.api.ChainGetRandomnessFromTickets(ctx, r.head, pers, round, entropy) if err != nil { return ret, err } r.reporter.Logf("fetched and recorded chain randomness for: dst=%d, epoch=%d, entropy=%x, result=%x", pers, round, entropy, ret) match := schema.RandomnessMatch{ On: schema.RandomnessRule{ Kind: schema.RandomnessChain, DomainSeparationTag: int64(pers), Epoch: int64(round), Entropy: entropy, }, Return: []byte(ret), } r.lk.Lock() r.recorded = append(r.recorded, match) r.lk.Unlock() return ret, err } func (r *RecordingRand) GetBeaconRandomnessV3(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { return r.getBeaconRandomness(ctx, pers, round, entropy) } func (r *RecordingRand) GetBeaconRandomnessV1(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { return r.getBeaconRandomness(ctx, pers, round, entropy) } func (r *RecordingRand) GetBeaconRandomnessV2(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { return r.getBeaconRandomness(ctx, pers, round, entropy) } func (r *RecordingRand) getBeaconRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { r.once.Do(r.loadHead) ret, err := r.api.StateGetRandomnessFromBeacon(ctx, pers, round, entropy, r.head) if err != nil { return ret, err } r.reporter.Logf("fetched and recorded beacon randomness for: dst=%d, epoch=%d, entropy=%x, result=%x", pers, round, entropy, ret) match := schema.RandomnessMatch{ On: schema.RandomnessRule{ Kind: schema.RandomnessBeacon, DomainSeparationTag: int64(pers), Epoch: int64(round), Entropy: entropy, }, Return: []byte(ret), } 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() return r.recorded }