diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 8ba8cd334e..be6f669f46 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -282,7 +282,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM // Execute the call. msg := callmsg{call} - evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain, nil) + evmContext := vm.NewEVMContext(msg, block.Header(), b.blockchain, nil) // Create a new environment which holds all relevant information // about the transaction and calling mechanisms. vmenv := vm.NewEVM(evmContext, statedb, b.config, vm.Config{}) diff --git a/consensus/istanbul/backend/engine.go b/consensus/istanbul/backend/engine.go index 11c7dd0d70..3ad665265d 100644 --- a/consensus/istanbul/backend/engine.go +++ b/consensus/istanbul/backend/engine.go @@ -53,7 +53,7 @@ var ( // address. errInvalidSignature = errors.New("invalid signature") // errInsufficientSeals is returned when there is not enough signatures to - // pass the 2F+1 quorum check. + // pass the quorum check. errInsufficientSeals = errors.New("not enough seals to reach quorum") // errUnknownBlock is returned when the list of validators or header is requested for a block // that is not part of the local blockchain. @@ -111,8 +111,7 @@ func (sb *Backend) Author(header *types.Header) (common.Address, error) { } // VerifyHeader checks whether a header conforms to the consensus rules of a -// given engine. Verifying the seal may be done optionally here, or explicitly -// via the VerifySeal method. +// given engine. Verifies the seal regardless of given "seal" argument. func (sb *Backend) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error { return sb.verifyHeader(chain, header, nil) } @@ -350,17 +349,24 @@ func (sb *Backend) verifyAggregatedSeal(headerHash common.Hash, validators istan // VerifySeal checks whether the crypto seal on a header is valid according to // the consensus rules of the given engine. func (sb *Backend) VerifySeal(chain consensus.ChainReader, header *types.Header) error { - // get parent header and ensure the signer is in parent's validator set - number := header.Number.Uint64() - if number == 0 { + // Ensure the block number is greater than zero, but less or equal to than max uint64. + if header.Number.Cmp(common.Big0) <= 0 || !header.Number.IsUint64() { return errUnknownBlock } - // ensure that the difficulty equals to defaultDifficulty - if header.Difficulty.Cmp(defaultDifficulty) != 0 { - return errInvalidDifficulty + extra, err := types.ExtractIstanbulExtra(header) + if err != nil { + return errInvalidExtraDataFormat } - return sb.verifySigner(chain, header, nil) + + // Acquire the validator set whose signatures will be verified. + // FIXME: Based on the current implemenation of validator set construction, only validator sets + // from the canonical chain will be used. This means that if the provided header is a valid + // member of a non-canonical chain, seal verification will only succeed if the validator set + // happens to be the same as the canonical chain at the same block number (as would be the case + // for a fork from the canonical chain which does not cross an epoch boundary) + valSet := sb.getValidators(header.Number.Uint64()-1, header.ParentHash) + return sb.verifyAggregatedSeal(header.Hash(), valSet, extra.AggregatedSeal) } // Prepare initializes the consensus fields of a block header according to the @@ -697,16 +703,15 @@ func (sb *Backend) Stop() error { // snapshot retrieves the validator set needed to sign off on the block immediately after 'number'. E.g. if you need to find the validator set that needs to sign off on block 6, // this method should be called with number set to 5. // -// hash - The requested snapshot's block's hash +// hash - The requested snapshot's block's hash. Only used for snapshot cache storage. // number - The requested snapshot's block number // parents - (Optional argument) An array of headers from directly previous blocks. func (sb *Backend) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) { // Search for a snapshot in memory or on disk var ( - headers []*types.Header - header *types.Header - snap *Snapshot - blockHash common.Hash + headers []*types.Header + header *types.Header + snap *Snapshot ) numberIter := number @@ -730,7 +735,8 @@ func (sb *Backend) snapshot(chain consensus.ChainReader, number uint64, hash com break } - if numberIter == number { + var blockHash common.Hash + if numberIter == number && hash != (common.Hash{}) { blockHash = hash } else { header = chain.GetHeaderByNumber(numberIter) diff --git a/consensus/istanbul/backend/engine_test.go b/consensus/istanbul/backend/engine_test.go index 47feddadfd..7e70079377 100644 --- a/consensus/istanbul/backend/engine_test.go +++ b/consensus/istanbul/backend/engine_test.go @@ -482,17 +482,41 @@ func TestVerifySeal(t *testing.T) { } block := makeBlock(chain, engine, genesis) - // change block content + + // change header content and expect to invalidate signature header := block.Header() header.Number = big.NewInt(4) - block1 := block.WithSeal(header) - err = engine.VerifySeal(chain, block1.Header()) - if err != errUnauthorized { - t.Errorf("error mismatch: have %v, want %v", err, errUnauthorized) + err = engine.VerifySeal(chain, header) + if err != errInvalidSignature { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidSignature) + } + + // delete istanbul extra data and expect invalid extra data format + header = block.Header() + header.Extra = nil + err = engine.VerifySeal(chain, header) + if err != errInvalidExtraDataFormat { + t.Errorf("error mismatch: have %v, want %v", err, errInvalidExtraDataFormat) + } + + // modify seal bitmap and expect to fail the quorum check + header = block.Header() + extra, err := types.ExtractIstanbulExtra(header) + if err != nil { + t.Fatalf("failed to extract istanbul data: %v", err) + } + extra.AggregatedSeal.Bitmap = big.NewInt(0) + encoded, err := rlp.EncodeToBytes(extra) + if err != nil { + t.Fatalf("failed to encode istanbul data: %v", err) + } + header.Extra = append(header.Extra[:types.IstanbulExtraVanity], encoded...) + err = engine.VerifySeal(chain, header) + if err != errInsufficientSeals { + t.Errorf("error mismatch: have %v, want %v", err, errInsufficientSeals) } - // unauthorized users but still can get correct signer address - engine.Authorize(common.Address{}, nil, nil, nil) + // verifiy the seal on the unmodified block. err = engine.VerifySeal(chain, block.Header()) if err != nil { t.Errorf("error mismatch: have %v, want nil", err) diff --git a/consensus/istanbul/backend/pos.go b/consensus/istanbul/backend/pos.go index 57927b25d5..c916cf4dd1 100644 --- a/consensus/istanbul/backend/pos.go +++ b/consensus/istanbul/backend/pos.go @@ -149,17 +149,17 @@ func (sb *Backend) distributeEpochPayments(header *types.Header, state *state.St func (sb *Backend) distributeEpochRewards(header *types.Header, state *state.StateDB, valSet []istanbul.Validator, maxTotalRewards *big.Int, uptimes []*big.Int) (*big.Int, error) { totalEpochRewards := big.NewInt(0) - // Fixed epoch reward to the infrastructure fund. + // Fixed epoch reward to the community fund. // TODO(asa): This should be a fraction of the overall reward to stakers. - infrastructureEpochReward := big.NewInt(params.Ether) + communityEpochReward := big.NewInt(params.Ether) governanceAddress, err := contract_comm.GetRegisteredAddress(params.GovernanceRegistryId, header, state) if err != nil { return totalEpochRewards, err } if governanceAddress != nil { - state.AddBalance(*governanceAddress, infrastructureEpochReward) - totalEpochRewards.Add(totalEpochRewards, infrastructureEpochReward) + state.AddBalance(*governanceAddress, communityEpochReward) + totalEpochRewards.Add(totalEpochRewards, communityEpochReward) } // Select groups that elected at least one validator aggregate their uptimes. diff --git a/contract_comm/evm.go b/contract_comm/evm.go index 47eb7138e7..94641b62a7 100644 --- a/contract_comm/evm.go +++ b/contract_comm/evm.go @@ -22,111 +22,20 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/contract_comm/errors" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" ) -// NOTE: Any changes made to this file should be duplicated to core/evm.go! - var ( emptyMessage = types.NewMessage(common.HexToAddress("0x0"), nil, 0, common.Big0, 0, common.Big0, nil, nil, common.Big0, []byte{}, false) internalEvmHandlerSingleton *InternalEVMHandler ) -// TODO(kevjue) - Figure out a way to not have duplicated code between this file and core/evm.go - -// ChainContext supports retrieving chain data and consensus parameters -// from the blockchain to be used during transaction processing. -type ChainContext interface { - // Engine retrieves the blockchain's consensus engine. - Engine() consensus.Engine - - // GetHeader returns the current header. - GetHeader(common.Hash, uint64) *types.Header - - // GetVMConfig returns the node's vm configuration - GetVMConfig() *vm.Config - - CurrentHeader() *types.Header - - State() (*state.StateDB, error) - - // Config returns the blockchain's chain configuration - Config() *params.ChainConfig -} - -// NewEVMContext creates a new context for use in the EVM. -func NewEVMContext(msg types.Message, header *types.Header, chain ChainContext, author *common.Address) vm.Context { - // If we don't have an explicit author (i.e. not mining), extract from the header - var beneficiary common.Address - if author == nil { - beneficiary, _ = chain.Engine().Author(header) // Ignore error, we're past header validation - } else { - beneficiary = *author - } - - return vm.Context{ - CanTransfer: CanTransfer, - Transfer: Transfer, - GetHash: GetHashFn(header, chain), - Origin: msg.From(), - Coinbase: beneficiary, - BlockNumber: new(big.Int).Set(header.Number), - Time: new(big.Int).Set(header.Time), - Difficulty: new(big.Int).Set(header.Difficulty), - GasLimit: header.GasLimit, - GasPrice: new(big.Int).Set(msg.GasPrice()), - Header: header, - Engine: chain.Engine(), - } -} - -// GetHashFn returns a GetHashFunc which retrieves header hashes by number -func GetHashFn(ref *types.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 { - cache = map[uint64]common.Hash{ - ref.Number.Uint64() - 1: ref.ParentHash, - } - } - // 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 (up to a limit of 256) - for i, header := 0, chain.GetHeader(ref.ParentHash, ref.Number.Uint64()-1); header != nil && i <= 256; i, header = i+1, chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) { - cache[header.Number.Uint64()-1] = header.ParentHash - if n == header.Number.Uint64()-1 { - return header.ParentHash - } - } - return common.Hash{} - } -} - -// CanTransfer checks whether there are enough funds in the address's account to make a transfer. -// This does not take the necessary gas into account to make the transfer valid. -func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool { - return db.GetBalance(addr).Cmp(amount) >= 0 -} - -// Transfer subtracts amount from sender and adds amount to recipient using the given Db -func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { - db.SubBalance(sender, amount) - db.AddBalance(recipient, amount) -} - // An EVM handler to make calls to smart contracts from within geth type InternalEVMHandler struct { - chain ChainContext + chain vm.ChainContext } func MakeStaticCall(registryId [32]byte, abi abi.ABI, funcName string, args []interface{}, returnObj interface{}, gas uint64, header *types.Header, state vm.StateDB) (uint64, error) { @@ -177,7 +86,7 @@ func createEVM(header *types.Header, state vm.StateDB) (*vm.EVM, error) { // The EVM Context requires a msg, but the actual field values don't really matter for this case. // Putting in zero values. - context := NewEVMContext(emptyMessage, header, internalEvmHandlerSingleton.chain, nil) + context := vm.NewEVMContext(emptyMessage, header, internalEvmHandlerSingleton.chain, nil) evm := vm.NewEVM(context, state, internalEvmHandlerSingleton.chain.Config(), *internalEvmHandlerSingleton.chain.GetVMConfig()) return evm, nil @@ -204,7 +113,7 @@ func makeCallFromSystem(scAddress common.Address, abi abi.ABI, funcName string, return gasLeft, nil } -func SetInternalEVMHandler(chain ChainContext) { +func SetInternalEVMHandler(chain vm.ChainContext) { if internalEvmHandlerSingleton == nil { log.Trace("Setting the InternalEVMHandler Singleton") internalEvmHandler := InternalEVMHandler{ diff --git a/core/chain_makers.go b/core/chain_makers.go index b47d491590..d11a089736 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -91,7 +91,7 @@ func (b *BlockGen) AddTx(tx *types.Transaction) { // further limitations on the content of transactions that can be // added. If contract code relies on the BLOCKHASH instruction, // the block in chain will be returned. -func (b *BlockGen) AddTxWithChain(bc ChainContext, tx *types.Transaction) { +func (b *BlockGen) AddTxWithChain(bc vm.ChainContext, tx *types.Transaction) { if b.gasPool == nil { b.SetCoinbase(common.Address{}) } diff --git a/core/state_processor.go b/core/state_processor.go index c1620bdda8..febc2ad38b 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -105,14 +105,14 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg // and uses the input parameters for its environment. It returns the receipt // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, header *types.Header, statedb *state.StateDB, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, uint64, error) { +func ApplyTransaction(config *params.ChainConfig, bc vm.ChainContext, author *common.Address, gp *GasPool, header *types.Header, statedb *state.StateDB, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, uint64, error) { msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) if err != nil { return nil, 0, err } // Create a new context to be used in the EVM environment - context := NewEVMContext(msg, header, bc, author) + context := vm.NewEVMContext(msg, header, bc, author) // Create a new environment which holds all relevant information // about the transaction and calling mechanisms. vmenv := vm.NewEVM(context, statedb, config, cfg) diff --git a/core/state_transition.go b/core/state_transition.go index 67c3306094..bddfe49a26 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -59,7 +59,7 @@ The state transitioning model does all the necessary work to work out a valid ne */ type StateTransition struct { gp *GasPool - msg Message + msg vm.Message gas uint64 gasPrice *big.Int initialGas uint64 @@ -70,28 +70,6 @@ type StateTransition struct { gasPriceMinimum *big.Int } -// Message represents a message sent to a contract. -type Message interface { - From() common.Address - //FromFrontier() (common.Address, error) - To() *common.Address - - GasPrice() *big.Int - Gas() uint64 - - // FeeCurrency specifies the currency for gas and gateway fees. - // nil correspond to Celo Gold (native currency). - // All other values should correspond to ERC20 contract addresses extended to be compatible with gas payments. - FeeCurrency() *common.Address - GatewayFeeRecipient() *common.Address - GatewayFee() *big.Int - Value() *big.Int - - Nonce() uint64 - CheckNonce() bool - Data() []byte -} - // IntrinsicGas computes the 'intrinsic gas' for a message with the given data. func IntrinsicGas(data []byte, contractCreation, homestead bool, header *types.Header, state vm.StateDB, feeCurrency *common.Address) (uint64, error) { // Set the starting gas for the raw transaction @@ -143,7 +121,7 @@ func IntrinsicGas(data []byte, contractCreation, homestead bool, header *types.H } // NewStateTransition initialises and returns a new state transition object. -func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition { +func NewStateTransition(evm *vm.EVM, msg vm.Message, gp *GasPool) *StateTransition { gasPriceMinimum, _ := gpm.GetGasPriceMinimum(msg.FeeCurrency(), evm.GetHeader(), evm.GetStateDB()) return &StateTransition{ @@ -165,7 +143,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition // the gas used (which includes gas refunds) and an error if it failed. An error always // indicates a core error meaning that the message would always fail for that particular // state and would never be accepted within a block. -func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) { +func ApplyMessage(evm *vm.EVM, msg vm.Message, gp *GasPool) ([]byte, uint64, bool, error) { log.Trace("Applying state transition message", "from", msg.From(), "nonce", msg.Nonce(), "to", msg.To(), "gas price", msg.GasPrice(), "fee currency", msg.FeeCurrency(), "gateway fee recipient", msg.GatewayFeeRecipient(), "gateway fee", msg.GatewayFee(), "gas", msg.Gas(), "value", msg.Value(), "data", msg.Data()) return NewStateTransition(evm, msg, gp).TransitionDb() } @@ -408,13 +386,13 @@ func (st *StateTransition) distributeTxFees() error { return err } - // Send the base of the transaction fee to the infrastructure fund. + // Send the base of the transaction fee to the community fund. governanceAddress, err := vm.GetRegisteredAddressWithEvm(params.GovernanceRegistryId, st.evm) if err != nil { if err != commerrs.ErrSmartContractNotDeployed && err != commerrs.ErrRegistryContractNotDeployed { return err } - log.Trace("Cannot credit gas fee to infrastructure fund: refunding fee to sender", "error", err, "fee", baseTxFee) + log.Trace("Cannot credit gas fee to community fund: refunding fee to sender", "error", err, "fee", baseTxFee) refund.Add(refund, baseTxFee) } else { log.Trace("Crediting gas fee tip", "recipient", *governanceAddress, "amount", baseTxFee, "feeCurrency", st.msg.FeeCurrency()) diff --git a/core/evm.go b/core/vm/context.go similarity index 50% rename from core/evm.go rename to core/vm/context.go index 42a0a3df2e..ab8b1f66ae 100644 --- a/core/evm.go +++ b/core/vm/context.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package core +package vm import ( "math/big" @@ -23,23 +23,48 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" ) -// NOTE: Any changes made to this file should be duplicated to contract_comm/evm.go! +// Message represents a message sent to a contract. +type Message interface { + From() common.Address + //FromFrontier() (common.Address, error) + To() *common.Address + + GasPrice() *big.Int + Gas() uint64 + + // FeeCurrency specifies the currency for gas and gateway fees. + // nil correspond to Celo Gold (native currency). + // All other values should correspond to ERC20 contract addresses extended to be compatible with gas payments. + FeeCurrency() *common.Address + GatewayFeeRecipient() *common.Address + GatewayFee() *big.Int + Value() *big.Int + + Nonce() uint64 + CheckNonce() bool + Data() []byte +} // ChainContext supports retrieving chain data and consensus parameters -// from the block chain to be used during transaction processing. +// from the blockchain to be used during transaction processing. type ChainContext interface { // Engine retrieves the blockchain's consensus engine. Engine() consensus.Engine - // GetHeader returns the hash corresponding to their hash. + // GetHeader returns the hash corresponding to the given hash and number. GetHeader(common.Hash, uint64) *types.Header + // GetHeaderByNumber returns the hash corresponding number. + // FIXME: Use of this function, as implemented, in the EVM context produces undefined behavior + // in the pressence of forks. A new method needs to be created to retrieve a header by number + // in the correct fork. + GetHeaderByNumber(uint64) *types.Header + // GetVMConfig returns the node's vm configuration - GetVMConfig() *vm.Config + GetVMConfig() *Config CurrentHeader() *types.Header @@ -50,7 +75,7 @@ type ChainContext interface { } // NewEVMContext creates a new context for use in the EVM. -func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author *common.Address) vm.Context { +func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author *common.Address) Context { // If we don't have an explicit author (i.e. not mining), extract from the header var beneficiary common.Address if author == nil { @@ -60,27 +85,31 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author } var engine consensus.Engine + var getHeaderByNumberFn func(uint64) *types.Header if chain != nil { engine = chain.Engine() + getHeaderByNumberFn = chain.GetHeaderByNumber } - return vm.Context{ - CanTransfer: CanTransfer, - Transfer: Transfer, - GetHash: GetHashFn(header, chain), - Origin: msg.From(), - Coinbase: beneficiary, - BlockNumber: new(big.Int).Set(header.Number), - Time: new(big.Int).Set(header.Time), - Difficulty: new(big.Int).Set(header.Difficulty), - GasLimit: header.GasLimit, - GasPrice: new(big.Int).Set(msg.GasPrice()), - Engine: engine, + return Context{ + CanTransfer: CanTransfer, + Transfer: Transfer, + GetHash: GetHashFn(header, chain), + GetHeaderByNumber: getHeaderByNumberFn, + VerifySeal: VerifySealFn(header, chain), + Origin: msg.From(), + Coinbase: beneficiary, + BlockNumber: new(big.Int).Set(header.Number), + Time: new(big.Int).Set(header.Time), + Difficulty: new(big.Int).Set(header.Difficulty), + GasLimit: header.GasLimit, + GasPrice: new(big.Int).Set(msg.GasPrice()), + Engine: engine, } } // GetHashFn returns a GetHashFunc which retrieves header hashes by number -func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash { +func GetHashFn(ref *types.Header, chain ChainContext) func(uint64) common.Hash { var cache map[uint64]common.Hash return func(n uint64) common.Hash { @@ -106,13 +135,34 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) 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 { +// This does not take the necessary gas into account to make the transfer valid. +func CanTransfer(db StateDB, addr common.Address, amount *big.Int) bool { return db.GetBalance(addr).Cmp(amount) >= 0 } // Transfer subtracts amount from sender and adds amount to recipient using the given Db -func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { +func Transfer(db StateDB, sender, recipient common.Address, amount *big.Int) { db.SubBalance(sender, amount) db.AddBalance(recipient, amount) } + +// VerifySealFn returns a function which returns true when the given header has a verifiable seal. +func VerifySealFn(ref *types.Header, chain ChainContext) func(*types.Header) bool { + return func(header *types.Header) bool { + // If the block is later than the unsealed reference block, return false. + if header.Number.Cmp(ref.Number) > 0 { + return false + } + + // FIXME: Implementation currently relies on the Istanbul engine's internal view of the + // chain, so return false if this is not an Istanbul chain. As a consequence of this the + // seal is always verified against the canonical chain, which makes behavior undefined if + // this function is evaluated on a chain which does not have the highest total difficulty. + if chain.Config().Istanbul == nil { + return false + } + + // Submit the header to the engine's seal verification function. + return chain.Engine().VerifySeal(nil, header) == nil + } +} diff --git a/core/vm/contracts.go b/core/vm/contracts.go index e35b675c0d..c376a8a323 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -53,15 +53,24 @@ var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{4}): &dataCopy{}, } -var CeloPrecompiledContractsAddressOffset = byte(0xff) -var transferAddress = common.BytesToAddress(append([]byte{0}, (CeloPrecompiledContractsAddressOffset - 2))) -var fractionMulExpAddress = common.BytesToAddress(append([]byte{0}, (CeloPrecompiledContractsAddressOffset - 3))) -var proofOfPossessionAddress = common.BytesToAddress(append([]byte{0}, (CeloPrecompiledContractsAddressOffset - 4))) -var getValidatorAddress = common.BytesToAddress(append([]byte{0}, (CeloPrecompiledContractsAddressOffset - 5))) -var numberValidatorsAddress = common.BytesToAddress(append([]byte{0}, (CeloPrecompiledContractsAddressOffset - 6))) -var epochSizeAddress = common.BytesToAddress(append([]byte{0}, (CeloPrecompiledContractsAddressOffset - 7))) -var blockNumberFromHeaderAddress = common.BytesToAddress(append([]byte{0}, (CeloPrecompiledContractsAddressOffset - 8))) -var hashHeaderAddress = common.BytesToAddress(append([]byte{0}, (CeloPrecompiledContractsAddressOffset - 9))) +func celoPrecompileAddress(index byte) common.Address { + return common.BytesToAddress(append([]byte{0}, (CeloPrecompiledContractsAddressOffset - index))) +} + +var ( + CeloPrecompiledContractsAddressOffset = byte(0xff) + + transferAddress = celoPrecompileAddress(2) + fractionMulExpAddress = celoPrecompileAddress(3) + proofOfPossessionAddress = celoPrecompileAddress(4) + getValidatorAddress = celoPrecompileAddress(5) + numberValidatorsAddress = celoPrecompileAddress(6) + epochSizeAddress = celoPrecompileAddress(7) + blockNumberFromHeaderAddress = celoPrecompileAddress(8) + hashHeaderAddress = celoPrecompileAddress(9) + getParentSealBitmapAddress = celoPrecompileAddress(10) + getVerifiedSealBitmapAddress = celoPrecompileAddress(11) +) // PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum // contracts used in the Byzantium release. @@ -84,6 +93,8 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{ epochSizeAddress: &epochSize{}, blockNumberFromHeaderAddress: &blockNumberFromHeader{}, hashHeaderAddress: &hashHeader{}, + getParentSealBitmapAddress: &getParentSealBitmap{}, + getVerifiedSealBitmapAddress: &getVerifiedSealBitmap{}, } // RunPrecompiledContract runs and evaluates the output of a precompiled contract. @@ -443,7 +454,6 @@ func (c *transfer) RequiredGas(input []byte) uint64 { func (c *transfer) Run(input []byte, caller common.Address, evm *EVM, gas uint64) ([]byte, uint64, error) { celoGoldAddress, err := GetRegisteredAddressWithEvm(params.GoldTokenRegistryId, evm) - if err != nil { return nil, gas, err } @@ -604,25 +614,39 @@ func (c *getValidator) RequiredGas(input []byte) uint64 { return params.GetValidatorGas } -// Return the validators that are required to sign this current, possibly unsealed, block. If this block is +// Return the validators that are required to sign the given, possibly unsealed, block number. If this block is // the last in an epoch, note that that may mean one or more of those validators may no longer be elected // for subsequent blocks. +// WARNING: Validator set is always constructed from the canonical chain, therefore this precompile is undefined +// if the engine is aware of a chain with higher total difficulty. func (c *getValidator) Run(input []byte, caller common.Address, evm *EVM, gas uint64) ([]byte, uint64, error) { gas, err := debitRequiredGas(c, input, gas) if err != nil { return nil, gas, err } - // input is comprised of a single argument: + // input is comprised of two arguments: // index: 32 byte integer representing the index of the validator to get - if len(input) < 32 { + // blockNumber: 32 byte integer representing the block number to access + if len(input) < 64 { return nil, gas, ErrInputLength } - index := (&big.Int{}).SetBytes(input[0:32]) + index := new(big.Int).SetBytes(input[0:32]) + + blockNumber := new(big.Int).SetBytes(input[32:64]) + if blockNumber.Cmp(common.Big0) == 0 { + // Validator set for the genesis block is empty, so any index is out of bounds. + return nil, gas, ErrValidatorsOutOfBounds + } + if blockNumber.Cmp(evm.Context.BlockNumber) > 0 { + return nil, gas, ErrBlockNumberOutOfBounds + } - validators := evm.Context.Engine.GetValidators(big.NewInt(evm.Context.BlockNumber.Int64()-1), evm.Context.GetHash(evm.Context.BlockNumber.Uint64()-1)) + // Note: Passing empty hash as here as it is an extra expense and the hash is not actually used. + validators := evm.Context.Engine.GetValidators(new(big.Int).Sub(blockNumber, common.Big1), common.Hash{}) + // Ensure index, which is guaranteed to be non-negative, is valid. if index.Cmp(big.NewInt(int64(len(validators)))) >= 0 { return nil, gas, ErrValidatorsOutOfBounds } @@ -642,17 +666,31 @@ func (c *numberValidators) RequiredGas(input []byte) uint64 { // Return the number of validators that are required to sign this current, possibly unsealed, block. If this block is // the last in an epoch, note that that may mean one or more of those validators may no longer be elected // for subsequent blocks. +// WARNING: Validator set is always constructed from the canonical chain, therefore this precompile is undefined +// if the engine is aware of a chain with higher total difficulty. func (c *numberValidators) Run(input []byte, caller common.Address, evm *EVM, gas uint64) ([]byte, uint64, error) { gas, err := debitRequiredGas(c, input, gas) if err != nil { return nil, gas, err } - if len(input) != 0 { + // input is comprised of a single argument: + // blockNumber: 32 byte integer representing the block number to access + if len(input) < 32 { return nil, gas, ErrInputLength } - validators := evm.Context.Engine.GetValidators(big.NewInt(evm.Context.BlockNumber.Int64()-1), evm.Context.GetHash(evm.Context.BlockNumber.Uint64()-1)) + blockNumber := new(big.Int).SetBytes(input[0:32]) + if blockNumber.Cmp(common.Big0) == 0 { + // Genesis validator set is empty. Return 0. + return make([]byte, 32), gas, nil + } + if blockNumber.Cmp(evm.Context.BlockNumber) > 0 { + return nil, gas, ErrBlockNumberOutOfBounds + } + + // Note: Passing empty hash as here as it is an extra expense and the hash is not actually used. + validators := evm.Context.Engine.GetValidators(new(big.Int).Sub(blockNumber, common.Big1), common.Hash{}) numberValidators := big.NewInt(int64(len(validators))).Bytes() numberValidatorsBytes := common.LeftPadBytes(numberValidators[:], 32) @@ -670,7 +708,7 @@ func (c *epochSize) Run(input []byte, caller common.Address, evm *EVM, gas uint6 if err != nil || len(input) != 0 { return nil, gas, err } - epochSize := big.NewInt(0).SetUint64(evm.Context.Engine.EpochSize()).Bytes() + epochSize := new(big.Int).SetUint64(evm.Context.Engine.EpochSize()).Bytes() epochSizeBytes := common.LeftPadBytes(epochSize[:], 32) return epochSizeBytes, gas, nil @@ -691,7 +729,7 @@ func (c *blockNumberFromHeader) Run(input []byte, caller common.Address, evm *EV var header types.Header err = rlp.DecodeBytes(input, &header) if err != nil { - return nil, gas, err + return nil, gas, ErrInputDecode } blockNumber := header.Number.Bytes() @@ -715,10 +753,94 @@ func (c *hashHeader) Run(input []byte, caller common.Address, evm *EVM, gas uint var header types.Header err = rlp.DecodeBytes(input, &header) if err != nil { - return nil, gas, err + return nil, gas, ErrInputDecode } hashBytes := header.Hash().Bytes() return hashBytes, gas, nil } + +type getParentSealBitmap struct{} + +func (c *getParentSealBitmap) RequiredGas(input []byte) uint64 { + return params.GetParentSealBitmapGas +} + +// Return the signer bitmap from the parent seal of a past block in the chain. +// Requested parent seal must have occurred within 4 epochs of the current block number. +func (c *getParentSealBitmap) Run(input []byte, caller common.Address, evm *EVM, gas uint64) ([]byte, uint64, error) { + gas, err := debitRequiredGas(c, input, gas) + if err != nil { + return nil, gas, err + } + + // input is comprised of a single argument: + // blockNumber: 32 byte integer representing the block number to access + if len(input) < 32 { + return nil, gas, ErrInputLength + } + + blockNumber := new(big.Int).SetBytes(input[0:32]) + + // Ensure the request is for information from a previously sealed block. + if blockNumber.Cmp(common.Big0) == 0 || blockNumber.Cmp(evm.Context.BlockNumber) > 0 { + return nil, gas, ErrBlockNumberOutOfBounds + } + + // Ensure the request is for a sufficiently recent block to limit state expansion. + historyLimit := new(big.Int).SetUint64(evm.Context.Engine.EpochSize() * 4) + if blockNumber.Cmp(new(big.Int).Sub(evm.Context.BlockNumber, historyLimit)) <= 0 { + return nil, gas, ErrBlockNumberOutOfBounds + } + + header := evm.Context.GetHeaderByNumber(blockNumber.Uint64()) + if header == nil { + log.Error("Unexpected failure to retrieve block in getParentSealBitmap precompile", "blockNumber", blockNumber) + return nil, gas, ErrUnexpected + } + + extra, err := types.ExtractIstanbulExtra(header) + if err != nil { + log.Error("Header without Istanbul extra data encountered in getParentSealBitmap precompile", "blockNumber", blockNumber, "err", err) + return nil, gas, ErrEngineIncompatible + } + + return common.LeftPadBytes(extra.ParentAggregatedSeal.Bitmap.Bytes()[:], 32), gas, nil +} + +// getVerifiedSealBitmap is a precompile to verify the seal on a given header and extract its bitmap. +type getVerifiedSealBitmap struct{} + +func (c *getVerifiedSealBitmap) RequiredGas(input []byte) uint64 { + return params.GetVerifiedSealBitmapGas +} + +func (c *getVerifiedSealBitmap) Run(input []byte, caller common.Address, evm *EVM, gas uint64) ([]byte, uint64, error) { + gas, err := debitRequiredGas(c, input, gas) + if err != nil { + return nil, gas, err + } + + // input is comprised of a single argument: + // header: rlp encoded block header + var header types.Header + if err := rlp.DecodeBytes(input, &header); err != nil { + return nil, gas, ErrInputDecode + } + + // Verify the seal against the engine rules. + if !evm.Context.VerifySeal(&header) { + return nil, gas, ErrInputVerification + } + + // Extract the verified seal from the header. + extra, err := types.ExtractIstanbulExtra(&header) + if err != nil { + log.Error("Header without Istanbul extra data encountered in getVerifiedSealBitmap precompile", "extraData", header.Extra, "err", err) + // Seal verified by a non-Istanbul engine. Return an error. + return nil, gas, ErrEngineIncompatible + } + + return common.LeftPadBytes(extra.AggregatedSeal.Bitmap.Bytes()[:], 32), gas, nil +} diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 43f99cbf18..0ddccc95a5 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -17,13 +17,115 @@ package vm import ( + "errors" "fmt" "math/big" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/istanbul" + "github.com/ethereum/go-ethereum/consensus/istanbul/validator" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/sha3" ) +// mockEngine provides functions used by precompiles for testing. +type mockEngine struct { + consensus.Engine +} + +var magicTestNonce = types.BlockNonce{0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef} + +func (e mockEngine) VerifySeal(_ consensus.ChainReader, header *types.Header) error { + if header.Nonce == magicTestNonce { + return nil + } + return errors.New("missing magic nonce") +} + +func (e mockEngine) EpochSize() uint64 { + return 100 +} + +func (e mockEngine) GetValidators(number *big.Int, _ common.Hash) []istanbul.Validator { + preimage := append([]byte("fakevalidators"), common.LeftPadBytes(number.Bytes()[:], 32)...) + hash := sha3.Sum256(preimage) + var validators []istanbul.Validator + for i := 0; i < 16; i, hash = i+1, sha3.Sum256(hash[:]) { + validators = append(validators, validator.New(common.BytesToAddress(hash[:]), nil)) + } + return validators +} + +// mockChainContext provides functions used by precompiles for testing. +type mockChainContext struct { + ChainContext +} + +func makeTestSeal(number *big.Int) types.IstanbulAggregatedSeal { + preimage := append([]byte("fakeseal"), common.LeftPadBytes(number.Bytes()[:], 32)...) + hash := sha3.Sum256(preimage) + return types.IstanbulAggregatedSeal{Bitmap: new(big.Int).SetBytes(hash[:2])} +} + +func makeTestHeaderHash(number *big.Int) common.Hash { + preimage := append([]byte("fakeheader"), common.LeftPadBytes(number.Bytes()[:], 32)...) + return common.Hash(sha3.Sum256(preimage)) +} + +func makeTestHeaderExtra(number *big.Int) *types.IstanbulExtra { + return &types.IstanbulExtra{ + AggregatedSeal: makeTestSeal(number), + ParentAggregatedSeal: makeTestSeal(new(big.Int).Sub(number, common.Big1)), + } +} + +func makeTestHeader(number *big.Int) *types.Header { + extra, err := rlp.EncodeToBytes(makeTestHeaderExtra(number)) + if err != nil { + panic(err) + } + return &types.Header{ + ParentHash: makeTestHeaderHash(new(big.Int).Sub(number, common.Big1)), + Number: number, + GasLimit: params.DefaultGasLimit, + GasUsed: params.DefaultGasLimit / 2, + Extra: append(make([]byte, types.IstanbulExtraVanity), extra...), + Time: new(big.Int).Mul(number, big.NewInt(5)), + Difficulty: common.Big1, + } +} + +func (c mockChainContext) GetHeader(_ common.Hash, number uint64) *types.Header { + return makeTestHeader(new(big.Int).SetUint64(number)) +} + +func (c mockChainContext) GetHeaderByNumber(number uint64) *types.Header { + return makeTestHeader(new(big.Int).SetUint64(number)) +} + +func (c mockChainContext) Config() *params.ChainConfig { + return ¶ms.ChainConfig{Istanbul: ¶ms.IstanbulConfig{}} +} + +func (c mockChainContext) Engine() consensus.Engine { + return mockEngine{} +} + +// Create a global mock EVM for use in the following tests. +var mockEVM = &EVM{ + Context: NewEVMContext( + types.NewMessage(common.HexToAddress("a11ce"), nil, 0, common.Big0, 0, common.Big1, nil, nil, common.Big0, nil, false), + makeTestHeader(big.NewInt(10000)), + mockChainContext{}, + &common.Address{}, + ), +} + // precompiledTest defines the input/output pairs for precompiled contract tests. type precompiledTest struct { input, expected string @@ -368,13 +470,13 @@ var proofOfPossessionTests = []precompiledTest{ var hashHeaderTests = []precompiledTest{ { input: "", - expected: "EOF", + expected: "unable to decode input", name: "input_invalid_empty", errorExpected: true, }, { input: "f901f9a07285abd5b24742f184ad676e31f6054663b3529bc35ea2fcad8a3e0f642a46f7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ecc60e00b3fe5ce9f6e1a10e5469764daf51f1fe93c22ec3f9a7583a80357217a0d35d334d87c0cc0a202e3756bf81fae08b1575f286c7ee7a3f8df4f0f3afc55da056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845c47775c80a0000000000000000000000000000000000000000000000000000000000000000088000000000000000", - expected: "rlp: value size exceeds available input length", + expected: "unable to decode input", name: "input_invalid_removed_byte", errorExpected: true, }, @@ -388,13 +490,13 @@ var hashHeaderTests = []precompiledTest{ var blockNumberFromHeaderTests = []precompiledTest{ { input: "", - expected: "EOF", + expected: "unable to decode input", name: "input_invalid_empty", errorExpected: true, }, { input: "f901f9a07285abd5b24742f184ad676e31f6054663b3529bc35ea2fcad8a3e0f642a46f7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ecc60e00b3fe5ce9f6e1a10e5469764daf51f1fe93c22ec3f9a7583a80357217a0d35d334d87c0cc0a202e3756bf81fae08b1575f286c7ee7a3f8df4f0f3afc55da056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845c47775c80a0000000000000000000000000000000000000000000000000000000000000000088000000000000000", - expected: "rlp: value size exceeds available input length", + expected: "unable to decode input", name: "input_invalid_removed_byte", errorExpected: true, }, @@ -405,13 +507,166 @@ var blockNumberFromHeaderTests = []precompiledTest{ }, } +var getValidatorTests = []precompiledTest{ + // Input is { validator index | block number }. Output is validator address. + { + input: "", + expected: "invalid input length", + name: "input_invalid_empty", + errorExpected: true, + }, + { + input: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expected: "validator index out of bounds", + name: "invalid_genesis_block", + errorExpected: true, + }, + { + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff", + expected: "000000000000000000000000fa55ba38ef5f98473db2771dd03c17c02bbe0fdc", + name: "correct_block_0xff_index_0x0", + }, + { + input: "000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000ff", + expected: "00000000000000000000000068e2962e75d952ffabb71170783df3c2c85f7939", + name: "correct_block_0xff_index_0xa", + }, + { + input: "000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000ff", + expected: "validator index out of bounds", + name: "invalid_index_out_of_bounds", + errorExpected: true, + }, + { + input: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002710", + expected: "00000000000000000000000024e11f684408ce3e35772daa281facf81d8be157", + name: "correct_chain_head", + }, + { + input: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002711", + expected: "block number out of bounds", + name: "invalid_future_block", + errorExpected: true, + }, +} + +var numberValidatorsTests = []precompiledTest{ + // Input is block number. Output is validator set size. + { + input: "", + expected: "invalid input length", + name: "input_invalid_empty", + errorExpected: true, + }, + { + input: "0000000000000000000000000000000000000000000000000000000000000000", + expected: "0000000000000000000000000000000000000000000000000000000000000000", + name: "correct_genesis_block", + }, + { + input: "00000000000000000000000000000000000000000000000000000000000000ff", + expected: "0000000000000000000000000000000000000000000000000000000000000010", + name: "correct_block_0xff", + }, + { + input: "0000000000000000000000000000000000000000000000000000000000002710", + expected: "0000000000000000000000000000000000000000000000000000000000000010", + name: "correct_chain_head", + }, + { + input: "0000000000000000000000000000000000000000000000000000000000002711", + expected: "block number out of bounds", + name: "invalid_future_block", + errorExpected: true, + }, +} + +var getParentSealBitmapTests = []precompiledTest{ + // Input is block number. Output is bitmap. + { + input: "", + expected: "invalid input length", + name: "input_invalid_empty", + errorExpected: true, + }, + { + input: "0000000000000000000000000000000000000000000000000000000000000000", + expected: "block number out of bounds", + name: "invalid_genesis_block", + errorExpected: true, + }, + { + input: "0000000000000000000000000000000000000000000000000000000000002580", + expected: "block number out of bounds", + name: "invalid_outside_history_limit", + errorExpected: true, + }, + { + input: "0000000000000000000000000000000000000000000000000000000000002581", + expected: "000000000000000000000000000000000000000000000000000000000000645c", + name: "correct_last_block_in_history_limit", + }, + { + input: "0000000000000000000000000000000000000000000000000000000000002711", + expected: "block number out of bounds", + name: "invalid_chain_head_child", + errorExpected: true, + }, + { + input: "0000000000000000000000000000000000000000000000000000000000002710", + expected: "0000000000000000000000000000000000000000000000000000000000007ff0", + name: "correct_chain_head", + }, +} + +var getVerifiedSealBitmapTests = []precompiledTest{ + // Input is a block header. Output is bitmap. + { + input: "", + expected: "unable to decode input", + name: "input_invalid_empty", + errorExpected: true, + }, + { + input: func() string { + encoded, _ := rlp.EncodeToBytes(makeTestHeader(common.Big1)) + return hexutil.Encode(encoded)[2:] + }(), + expected: "unable to verify header", + name: "unverified_block_header", + errorExpected: true, + }, + { + input: func() string { + header := makeTestHeader(common.Big1) + header.Nonce = magicTestNonce + encoded, _ := rlp.EncodeToBytes(header) + return hexutil.Encode(encoded)[2:] + }(), + expected: "0000000000000000000000000000000000000000000000000000000000007b1d", + name: "correct_verified_header", + }, + { + input: func() string { + header := makeTestHeader(common.Big1) + header.Nonce = magicTestNonce + header.Extra = nil + encoded, _ := rlp.EncodeToBytes(header) + return hexutil.Encode(encoded)[2:] + }(), + expected: "blockchain engine incompatible with request", + name: "input_incompatible_engine", + errorExpected: true, + }, +} + func testPrecompiled(addr string, test precompiledTest, t *testing.T) { p := PrecompiledContractsByzantium[common.HexToAddress(addr)] in := common.Hex2Bytes(test.input) contract := NewContract(AccountRef(common.HexToAddress("1337")), nil, new(big.Int), p.RequiredGas(in)) t.Run(fmt.Sprintf("%s-Gas=%d", test.name, contract.Gas), func(t *testing.T) { - res, err := RunPrecompiledContract(p, in, contract, nil) + res, err := RunPrecompiledContract(p, in, contract, mockEVM) if test.errorExpected { if err == nil { t.Errorf("Expected error: %v, but no error occurred", test.expected) @@ -449,7 +704,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { for i := 0; i < bench.N; i++ { contract.Gas = reqGas copy(data, in) - res, err = RunPrecompiledContract(p, data, contract, nil) + res, err = RunPrecompiledContract(p, data, contract, mockEVM) } bench.StopTimer() //Check if it is correct @@ -576,6 +831,20 @@ func TestPrecompiledProofOfPossession(t *testing.T) { } } +// Tests sample inputs for getValidator +func TestGetValidator(t *testing.T) { + for _, test := range getValidatorTests { + testPrecompiled("fa", test, t) + } +} + +// Tests sample inputs for numberValidators +func TestNumberValidators(t *testing.T) { + for _, test := range numberValidatorsTests { + testPrecompiled("f9", test, t) + } +} + // Tests sample inputs for getBlockNumberFromHeader func TestGetBlockNumberFromHeader(t *testing.T) { for _, test := range blockNumberFromHeaderTests { @@ -589,3 +858,17 @@ func TestPrecompiledHashHeader(t *testing.T) { testPrecompiled("f6", test, t) } } + +// Tests sample inputs for getParentSealBitmapTests +func TestGetParentSealBitmap(t *testing.T) { + for _, test := range getParentSealBitmapTests { + testPrecompiled("f5", test, t) + } +} + +// Tests sample inputs for getParentSealBitmapTests +func TestGetVerifiedSealBitmap(t *testing.T) { + for _, test := range getVerifiedSealBitmapTests { + testPrecompiled("f4", test, t) + } +} diff --git a/core/vm/errors.go b/core/vm/errors.go index be6083b1aa..75636ffa63 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -27,6 +27,11 @@ var ( ErrInsufficientBalance = errors.New("insufficient balance for transfer") ErrContractAddressCollision = errors.New("contract address collision") ErrNoCompatibleInterpreter = errors.New("no compatible interpreter") - ErrValidatorsOutOfBounds = errors.New("getValidators out of bounds") + ErrValidatorsOutOfBounds = errors.New("validator index out of bounds") + ErrBlockNumberOutOfBounds = errors.New("block number out of bounds") ErrInputLength = errors.New("invalid input length") + ErrInputDecode = errors.New("unable to decode input") + ErrInputVerification = errors.New("unable to verify header") + ErrEngineIncompatible = errors.New("blockchain engine incompatible with request") + ErrUnexpected = errors.New("unexpected execution error") ) diff --git a/core/vm/evm.go b/core/vm/evm.go index ecdf7efd54..ff7601b99a 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -48,6 +48,11 @@ type ( // GetHashFunc returns the nth block hash in the blockchain // and is used by the BLOCKHASH EVM op code. GetHashFunc func(uint64) common.Hash + // GetHeaderByNumber returns the header of the nth block in the chain. + GetHeaderByNumberFunc func(uint64) *types.Header + // VerifySealFunc returns true if the given header contains a valid seal + // according to the engine's consensus rules. + VerifySealFunc func(*types.Header) bool ) // run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter. @@ -87,6 +92,10 @@ type Context struct { Transfer TransferFunc // GetHash returns the hash corresponding to n GetHash GetHashFunc + // GetParentSealBitmap returns the parent seal bitmap corresponding to n + GetHeaderByNumber GetHeaderByNumberFunc + // VerifySeal verifies or returns an error for the given header + VerifySeal VerifySealFunc // Message information Origin common.Address // Provides information for ORIGIN diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 31c9b9cf9d..f8c25266cb 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -18,14 +18,13 @@ package runtime import ( "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/vm" ) func NewEnv(cfg *Config) *vm.EVM { context := vm.Context{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, + CanTransfer: vm.CanTransfer, + Transfer: vm.Transfer, GetHash: func(uint64) common.Hash { return common.Hash{} }, Origin: cfg.Origin, diff --git a/eth/api_backend.go b/eth/api_backend.go index 6984fd1e87..423e379e96 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -124,11 +124,11 @@ func (b *EthAPIBackend) GetTd(blockHash common.Hash) *big.Int { return b.eth.blockchain.GetTdByHash(blockHash) } -func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, header *types.Header, state *state.StateDB) (*vm.EVM, func() error, error) { +func (b *EthAPIBackend) GetEVM(ctx context.Context, msg vm.Message, header *types.Header, state *state.StateDB) (*vm.EVM, func() error, error) { state.SetBalance(msg.From(), math.MaxBig256) vmError := func() error { return nil } - context := core.NewEVMContext(msg, header, b.eth.BlockChain(), nil) + context := vm.NewEVMContext(msg, header, b.eth.BlockChain(), nil) return vm.NewEVM(context, state, b.eth.chainConfig, *b.eth.blockchain.GetVMConfig()), vmError, nil } diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 5e980a4431..ffb7117147 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -206,7 +206,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl // Trace all the transactions contained within for i, tx := range task.block.Transactions() { msg, _ := tx.AsMessage(signer) - vmctx := core.NewEVMContext(msg, task.block.Header(), api.eth.blockchain, nil) + vmctx := vm.NewEVMContext(msg, task.block.Header(), api.eth.blockchain, nil) res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config) if err != nil { @@ -480,7 +480,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, // Fetch and execute the next transaction trace tasks for task := range jobs { msg, _ := txs[task.index].AsMessage(signer) - vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) + vmctx := vm.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config) if err != nil { @@ -499,7 +499,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, // Generate the next state snapshot fast without tracing msg, _ := tx.AsMessage(signer) - vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) + vmctx := vm.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) vmenv := vm.NewEVM(vmctx, statedb, api.config, vm.Config{}) if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { @@ -574,7 +574,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block // Prepare the trasaction for un-traced execution var ( msg, _ = tx.AsMessage(signer) - vmctx = core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) + vmctx = vm.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) vmConf vm.Config dump *os.File @@ -713,7 +713,7 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Ha // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. -func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.Context, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { +func (api *PrivateDebugAPI) traceTx(ctx context.Context, message vm.Message, vmctx vm.Context, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { // Assemble the structured logger or the JavaScript tracer var ( tracer vm.Tracer @@ -772,7 +772,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v } // computeTxEnv returns the execution environment of a certain transaction. -func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, reexec uint64) (core.Message, vm.Context, *state.StateDB, error) { +func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, reexec uint64) (vm.Message, vm.Context, *state.StateDB, error) { // Create the parent state database block := api.eth.blockchain.GetBlockByHash(blockHash) if block == nil { @@ -792,7 +792,7 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, ree for idx, tx := range block.Transactions() { // Assemble the transaction call message and return if the requested offset msg, _ := tx.AsMessage(signer) - ctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) + ctx := vm.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) if idx == txIndex { return msg, ctx, statedb, nil } diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 9b0c2c0130..15af341748 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -138,8 +138,8 @@ func TestPrestateTracerCreate2(t *testing.T) { */ origin, _ := signer.Sender(tx) context := vm.Context{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, + CanTransfer: vm.CanTransfer, + Transfer: vm.Transfer, Origin: origin, Coinbase: common.Address{}, BlockNumber: new(big.Int).SetUint64(8000000), @@ -229,7 +229,7 @@ func TestCallTracer(t *testing.T) { context := vm.Context{ CanTransfer: core.CanTransfer, - Transfer: core.Transfer, + Transfer: vm.Transfer, Origin: origin, Coinbase: test.Context.Miner, BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 30a647040c..996921b819 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -54,7 +54,7 @@ type Backend interface { GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) GetTd(blockHash common.Hash) *big.Int - GetEVM(ctx context.Context, msg core.Message, header *types.Header, state *state.StateDB) (*vm.EVM, func() error, error) + GetEVM(ctx context.Context, msg vm.Message, header *types.Header, state *state.StateDB) (*vm.EVM, func() error, error) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription diff --git a/les/api_backend.go b/les/api_backend.go index c8260cd75d..b8925111f2 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -105,10 +105,10 @@ func (b *LesApiBackend) GetTd(hash common.Hash) *big.Int { return b.eth.blockchain.GetTdByHash(hash) } -func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, header *types.Header, state *state.StateDB) (*vm.EVM, func() error, error) { +func (b *LesApiBackend) GetEVM(ctx context.Context, msg vm.Message, header *types.Header, state *state.StateDB) (*vm.EVM, func() error, error) { state.SetBalance(msg.From(), math.MaxBig256) - context := core.NewEVMContext(msg, header, b.eth.blockchain, nil) + context := vm.NewEVMContext(msg, header, b.eth.blockchain, nil) return vm.NewEVM(context, state, b.eth.chainConfig, vm.Config{}), state.Error, nil } diff --git a/les/odr_test.go b/les/odr_test.go index d907ddd6b5..b1bebbe104 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -135,7 +135,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), nil, nil, new(big.Int), data, false)} - context := core.NewEVMContext(msg, header, bc, nil) + context := vm.NewEVMContext(msg, header, bc, nil) vmenv := vm.NewEVM(context, statedb, config, vm.Config{}) //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{}) @@ -148,7 +148,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai state := light.NewState(ctx, header, lc.Odr()) state.SetBalance(testBankAddress, math.MaxBig256) msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), nil, nil, new(big.Int), data, false)} - context := core.NewEVMContext(msg, header, lc, nil) + context := vm.NewEVMContext(msg, header, lc, nil) vmenv := vm.NewEVM(context, state, config, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp) diff --git a/light/odr_test.go b/light/odr_test.go index c5df4eed7f..c53f029c10 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -180,7 +180,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain var ( st *state.StateDB header *types.Header - chain core.ChainContext + chain vm.ChainContext ) if bc == nil { chain = lc @@ -195,7 +195,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain // Perform read-only call. st.SetBalance(testBankAddress, math.MaxBig256) msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), nil, nil, new(big.Int), data, false)} - context := core.NewEVMContext(msg, header, chain, nil) + context := vm.NewEVMContext(msg, header, chain, nil) vmenv := vm.NewEVM(context, st, config, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp) diff --git a/params/protocol_params.go b/params/protocol_params.go index 7dd7af8b5a..16044f8e1d 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -100,6 +100,8 @@ const ( GetEpochSizeGas uint64 = 1000 // Cost of querying the number of blocks in an epoch. GetBlockNumberFromHeaderGas uint64 = 10000 // Cost of decoding a block header. HashHeaderGas uint64 = 20000 // Cost of hashing a block header. + GetParentSealBitmapGas uint64 = 500 // Cost of reading the parent seal bitmap from the chain. + GetVerifiedSealBitmapGas uint64 = 55000 // Cost of verifying the seal on a given RLP encoded header. ) var ( diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 5fbaaf0d27..0a05ae5c5e 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -133,7 +133,7 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) (*state.StateD if err != nil { return nil, err } - context := core.NewEVMContext(msg, block.Header(), nil, &t.json.Env.Coinbase) + context := vm.NewEVMContext(msg, block.Header(), nil, &t.json.Env.Coinbase) context.GetHash = vmTestBlockHash evm := vm.NewEVM(context, statedb, config, vmconfig) @@ -197,7 +197,7 @@ func (t *StateTest) genesis(config *params.ChainConfig) *core.Genesis { } } -func (tx *stTransaction) toMessage(ps stPostState) (core.Message, error) { +func (tx *stTransaction) toMessage(ps stPostState) (vm.Message, error) { // Derive sender from private key if present. var from common.Address if len(tx.PrivateKey) > 0 { diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go index cb81c5b94e..c63a91b202 100644 --- a/tests/vm_test_util.go +++ b/tests/vm_test_util.go @@ -127,7 +127,7 @@ func (t *VMTest) newEVM(statedb *state.StateDB, vmconfig vm.Config) *vm.EVM { initialCall = false return true } - return core.CanTransfer(db, address, amount) + return vm.CanTransfer(db, address, amount) } transfer := func(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {} context := vm.Context{