Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add precompiles to support signature checking on provided and historic headers #772

Merged
merged 28 commits into from
Dec 23, 2019
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
751819e
rename references to infrastructure fund to community fund
Dec 17, 2019
77f0972
add block number parameter to validator set precompiles
Dec 17, 2019
43f87e9
add get parent seal bitmap precompile
Dec 17, 2019
4833d97
Merge branch 'master' of github.com:celo-org/celo-blockchain into vic…
Dec 17, 2019
17f69c8
add get verified seal bitmap precompile
Dec 18, 2019
26daaa1
Merge branch 'master' of github.com:celo-org/celo-blockchain into vic…
Dec 18, 2019
5257684
small modifications
Dec 18, 2019
a3b1ae3
duplicate changes
Dec 18, 2019
5c40faa
fix VerifySeal test
Dec 18, 2019
fb0012b
Merge branch 'master' of github.com:celo-org/celo-blockchain into vic…
Dec 18, 2019
bcb6d49
address comments
Dec 19, 2019
6259948
fix code relied on by testing
Dec 19, 2019
2ec29ad
update gas cost
Dec 19, 2019
20f4f32
extract core/evm.go to core/vm/context.go
Dec 19, 2019
96cac26
fix test building
Dec 19, 2019
4f579f1
add note about the empty block hash
Dec 19, 2019
1e1fbec
replace GetParentSealBitmap with GetHeaderByNumber
Dec 19, 2019
67d1360
adjust gas cost
Dec 19, 2019
7fa1fa7
adjust notes on GetValidator call
Dec 19, 2019
083dee7
update error messages
Dec 19, 2019
ed4d32f
skip and verificatin check and change the error
Dec 19, 2019
4432ac2
add tests for new celo precompiles
Dec 19, 2019
9e812c1
address review comments
Dec 19, 2019
70c79fc
fix lint error
Dec 19, 2019
3d2353c
prevent panic in tests
Dec 20, 2019
fef3078
point ci to victor/slashing-precompiles
Dec 20, 2019
bd31aee
allow getting the parent seal form the unsealed block
Dec 20, 2019
56a2d47
Put CELO_MONOREPO_BRANCH_TO_TEST back to master
jfoutts-celo Dec 20, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ end-to-end-defaults: &end-to-end-defaults
docker:
- image: celohq/node10-gcloud
environment:
CELO_MONOREPO_BRANCH_TO_TEST: master
CELO_MONOREPO_BRANCH_TO_TEST: victor/slashing-precompiles

jobs:
unit-tests:
Expand Down
2 changes: 1 addition & 1 deletion accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{})
Expand Down
38 changes: 22 additions & 16 deletions consensus/istanbul/backend/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
nategraf marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
38 changes: 31 additions & 7 deletions consensus/istanbul/backend/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions consensus/istanbul/backend/pos.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
97 changes: 3 additions & 94 deletions contract_comm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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{
Expand Down
2 changes: 1 addition & 1 deletion core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{})
}
Expand Down
4 changes: 2 additions & 2 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
32 changes: 5 additions & 27 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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{
Expand All @@ -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()
}
Expand Down Expand Up @@ -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())
Expand Down
Loading