From 305ef3514a0d05ae24b5a75e10316daf7c8dab1a Mon Sep 17 00:00:00 2001 From: Rongjian Lan Date: Wed, 25 Aug 2021 11:40:05 -0700 Subject: [PATCH] add history vrf reading capability on vrf precompile (#3850) * precompile for history vrfs * remove prints --- core/evm.go | 41 +++++++++++++++++++++++++++++++++++++++ core/vm/evm.go | 25 ++++++++++++++++++++++-- core/vm/runtime/env.go | 1 + internal/params/config.go | 23 +++++++++++++++++++--- 4 files changed, 85 insertions(+), 5 deletions(-) diff --git a/core/evm.go b/core/evm.go index 608b79fbb..f4360253b 100644 --- a/core/evm.go +++ b/core/evm.go @@ -70,6 +70,7 @@ func NewEVMContext(msg Message, header *block.Header, chain ChainContext, author Transfer: Transfer, IsValidator: IsValidator, GetHash: GetHashFn(header, chain), + GetVRF: GetVRFFn(header, chain), Origin: msg.From(), Coinbase: beneficiary, BlockNumber: header.Number(), @@ -107,6 +108,46 @@ func GetHashFn(ref *block.Header, chain ChainContext) func(n uint64) common.Hash } } +// GetVRFFn returns a GetVRFFn which retrieves header vrf by number +func GetVRFFn(ref *block.Header, chain ChainContext) func(n uint64) common.Hash { + var cache map[uint64]common.Hash + + return func(n uint64) common.Hash { + // If there's no hash cache yet, make one + if cache == nil { + curVRF := common.Hash{} + if len(ref.Vrf()) >= 32 { + vrfAndProof := ref.Vrf() + copy(curVRF[:], vrfAndProof[:32]) + } + + cache = map[uint64]common.Hash{ + ref.Number().Uint64(): curVRF, + } + } + // Try to fulfill the request from the cache + if hash, ok := cache[n]; ok { + return hash + } + // Not cached, iterate the blocks and cache the hashes + for header := chain.GetHeader(ref.ParentHash(), ref.Number().Uint64()-1); header != nil; header = chain.GetHeader(header.ParentHash(), header.Number().Uint64()-1) { + + curVRF := common.Hash{} + if len(header.Vrf()) >= 32 { + vrfAndProof := header.Vrf() + copy(curVRF[:], vrfAndProof[:32]) + } + + cache[header.Number().Uint64()] = curVRF + + if n == header.Number().Uint64() { + return curVRF + } + } + return common.Hash{} + } +} + // CanTransfer checks whether there are enough funds in the address' account to make a transfer. // This does not take the necessary gas in to account to make the transfer valid. func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool { diff --git a/core/vm/evm.go b/core/vm/evm.go index 080e5febd..e311eebb4 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -42,6 +42,9 @@ type ( // GetHashFunc returns the nth block hash in the blockchain // and is used by the BLOCKHASH EVM op code. GetHashFunc func(uint64) common.Hash + // GetVRFFunc returns the nth block vrf in the blockchain + // and is used by the precompile VRF contract. + GetVRFFunc func(uint64) common.Hash ) // run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter. @@ -59,8 +62,24 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err } if p := precompiles[*contract.CodeAddr]; p != nil { if _, ok := p.(*vrf); ok { - // Override the input with vrf data of the current block so it can be returned to the contract program. - input = evm.Context.VRF.Bytes() + if evm.chainRules.IsPrevVRF { + requestedBlockNum := big.NewInt(0).SetBytes(input[0:32]) + minBlockNum := big.NewInt(0).Sub(evm.BlockNumber, common.Big257) + + if requestedBlockNum.Cmp(evm.BlockNumber) == 0 { + input = evm.Context.VRF.Bytes() + } else if requestedBlockNum.Cmp(minBlockNum) > 0 && requestedBlockNum.Cmp(evm.BlockNumber) < 0 { + // requested block number is in range + input = evm.GetVRF(requestedBlockNum.Uint64()).Bytes() + } else { + // else default to the current block's VRF + input = evm.Context.VRF.Bytes() + } + } else { + // Override the input with vrf data of the requested block so it can be returned to the contract program. + input = evm.Context.VRF.Bytes() + } + } return RunPrecompiledContract(p, input, contract) } @@ -92,6 +111,8 @@ type Context struct { Transfer TransferFunc // GetHash returns the hash corresponding to n GetHash GetHashFunc + // GetVRF returns the VRF corresponding to n + GetVRF GetVRFFunc // IsValidator determines whether the address corresponds to a validator or a smart contract // true: is a validator address; false: is smart contract address diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 3fa00ceb2..ab37b9709 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -29,6 +29,7 @@ func NewEnv(cfg *Config) *vm.EVM { Transfer: core.Transfer, IsValidator: core.IsValidator, GetHash: func(uint64) common.Hash { return common.Hash{} }, + GetVRF: func(uint64) common.Hash { return common.Hash{} }, Origin: cfg.Origin, Coinbase: cfg.Coinbase, diff --git a/internal/params/config.go b/internal/params/config.go index ea590e821..40efeaaf5 100644 --- a/internal/params/config.go +++ b/internal/params/config.go @@ -52,6 +52,7 @@ var ( RedelegationEpoch: big.NewInt(290), NoEarlyUnlockEpoch: big.NewInt(530), // Around Monday Apr 12th 2021, 22:30 UTC VRFEpoch: big.NewInt(631), // Around Wed July 7th 2021 + PrevVRFEpoch: EpochTBD, MinDelegation100Epoch: big.NewInt(631), // Around Wed July 7th 2021 MinCommissionRateEpoch: big.NewInt(631), // Around Wed July 7th 2021 MinCommissionPromoPeriod: big.NewInt(100), @@ -80,6 +81,7 @@ var ( RedelegationEpoch: big.NewInt(36500), NoEarlyUnlockEpoch: big.NewInt(73580), VRFEpoch: big.NewInt(73880), + PrevVRFEpoch: EpochTBD, MinDelegation100Epoch: big.NewInt(73880), MinCommissionRateEpoch: big.NewInt(73880), MinCommissionPromoPeriod: big.NewInt(10), @@ -109,6 +111,7 @@ var ( RedelegationEpoch: big.NewInt(0), NoEarlyUnlockEpoch: big.NewInt(0), VRFEpoch: big.NewInt(0), + PrevVRFEpoch: big.NewInt(0), MinDelegation100Epoch: big.NewInt(0), MinCommissionRateEpoch: big.NewInt(0), MinCommissionPromoPeriod: big.NewInt(10), @@ -138,6 +141,7 @@ var ( RedelegationEpoch: big.NewInt(0), NoEarlyUnlockEpoch: big.NewInt(0), VRFEpoch: big.NewInt(0), + PrevVRFEpoch: big.NewInt(0), MinDelegation100Epoch: big.NewInt(0), MinCommissionRateEpoch: big.NewInt(0), MinCommissionPromoPeriod: big.NewInt(10), @@ -167,6 +171,7 @@ var ( RedelegationEpoch: big.NewInt(0), NoEarlyUnlockEpoch: big.NewInt(0), VRFEpoch: big.NewInt(0), + PrevVRFEpoch: big.NewInt(0), MinDelegation100Epoch: big.NewInt(0), MinCommissionRateEpoch: big.NewInt(0), MinCommissionPromoPeriod: big.NewInt(10), @@ -195,6 +200,7 @@ var ( RedelegationEpoch: big.NewInt(0), NoEarlyUnlockEpoch: big.NewInt(0), VRFEpoch: big.NewInt(0), + PrevVRFEpoch: big.NewInt(0), MinDelegation100Epoch: big.NewInt(0), MinCommissionRateEpoch: big.NewInt(0), MinCommissionPromoPeriod: big.NewInt(10), @@ -225,6 +231,7 @@ var ( big.NewInt(0), // RedelegationEpoch big.NewInt(0), // NoEarlyUnlockEpoch big.NewInt(0), // VRFEpoch + big.NewInt(0), // PrevVRFEpoch big.NewInt(0), // MinDelegation100Epoch big.NewInt(0), // MinCommissionRateEpoch big.NewInt(10), // MinCommissionPromoPeriod @@ -255,6 +262,7 @@ var ( big.NewInt(0), // RedelegationEpoch big.NewInt(0), // NoEarlyUnlockEpoch big.NewInt(0), // VRFEpoch + big.NewInt(0), // PrevVRFEpoch big.NewInt(0), // MinDelegation100Epoch big.NewInt(0), // MinCommissionRateEpoch big.NewInt(10), // MinCommissionPromoPeriod @@ -342,6 +350,9 @@ type ChainConfig struct { // VRFEpoch is the epoch when VRF randomness is enabled VRFEpoch *big.Int `json:"vrf-epoch,omitempty"` + // PrevVRFEpoch is the epoch when previous VRF randomness can be fetched + PrevVRFEpoch *big.Int `json:"prev-vrf-epoch,omitempty"` + // MinDelegation100Epoch is the epoch when min delegation is reduced from 1000 ONE to 100 ONE MinDelegation100Epoch *big.Int `json:"min-delegation-100-epoch,omitempty"` @@ -450,6 +461,11 @@ func (c *ChainConfig) IsVRF(epoch *big.Int) bool { return isForked(c.VRFEpoch, epoch) } +// IsPrevVRF determines whether it is the epoch to enable previous vrf +func (c *ChainConfig) IsPrevVRF(epoch *big.Int) bool { + return isForked(c.PrevVRFEpoch, epoch) +} + // IsMinDelegation100 determines whether it is the epoch to reduce min delegation to 100 func (c *ChainConfig) IsMinDelegation100(epoch *big.Int) bool { return isForked(c.MinDelegation100Epoch, epoch) @@ -543,9 +559,9 @@ func isForked(s, epoch *big.Int) bool { // Rules is a one time interface meaning that it shouldn't be used in between transition // phases. type Rules struct { - ChainID *big.Int - EthChainID *big.Int - IsCrossLink, IsEIP155, IsS3, IsReceiptLog, IsIstanbul, IsVRF bool + ChainID *big.Int + EthChainID *big.Int + IsCrossLink, IsEIP155, IsS3, IsReceiptLog, IsIstanbul, IsVRF, IsPrevVRF bool } // Rules ensures c's ChainID is not nil. @@ -567,5 +583,6 @@ func (c *ChainConfig) Rules(epoch *big.Int) Rules { IsReceiptLog: c.IsReceiptLog(epoch), IsIstanbul: c.IsIstanbul(epoch), IsVRF: c.IsVRF(epoch), + IsPrevVRF: c.IsPrevVRF(epoch), } }