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 10 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
36 changes: 21 additions & 15 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, common.Hash{})
nategraf marked this conversation as resolved.
Show resolved Hide resolved
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,6 +735,7 @@ func (sb *Backend) snapshot(chain consensus.ChainReader, number uint64, hash com
break
}

var blockHash common.Hash
if numberIter == number {
blockHash = hash
} else {
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
96 changes: 80 additions & 16 deletions contract_comm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ var (
// 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.
// from the block chain to be used during transaction processing.
nategraf marked this conversation as resolved.
Show resolved Hide resolved
type ChainContext interface {
// Engine retrieves the blockchain's consensus engine.
Engine() consensus.Engine

// GetHeader returns the current header.
// GetHeader returns the hash corresponding to the given hash and number.
nategraf marked this conversation as resolved.
Show resolved Hide resolved
GetHeader(common.Hash, uint64) *types.Header

// GetVMConfig returns the node's vm configuration
Expand All @@ -70,24 +70,30 @@ func NewEVMContext(msg types.Message, header *types.Header, chain ChainContext,
beneficiary = *author
}

var engine consensus.Engine
if chain != nil {
nategraf marked this conversation as resolved.
Show resolved Hide resolved
engine = chain.Engine()
}

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(),
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: GetHashFn(header, chain),
GetParentSealBitmap: GetParentSealBitmapFn(header, chain),
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 {
Expand All @@ -112,7 +118,7 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash
}
}

// CanTransfer checks whether there are enough funds in the address's account to make a transfer.
// CanTransfer checks whether there are enough funds in the address' 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
Expand All @@ -124,6 +130,64 @@ func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int)
db.AddBalance(recipient, amount)
}

// GetParentSealFn returns a GetParentSeal function that returns the aggregated parent seal at the given block number.
// Note: Unless previously cached, retrieves every block between the reference block and n.
nategraf marked this conversation as resolved.
Show resolved Hide resolved
func GetParentSealBitmapFn(ref *types.Header, chain ChainContext) func(uint64) *big.Int {
nategraf marked this conversation as resolved.
Show resolved Hide resolved
var cache map[uint64]*big.Int

return func(n uint64) *big.Int {
// If the block is the unsealed reference block or later, return nil.
if n >= ref.Number.Uint64() {
return nil
}

// If there's no cache yet, make one
if cache == nil {
cache = make(map[uint64]*big.Int)
} else {
// Try to fulfill the request from the cache
if bitmap, ok := cache[n]; ok {
return bitmap
}
}

// Not cached, iterate the blocks and cache the hashes (not limited here)
for header := chain.GetHeader(ref.ParentHash, ref.Number.Uint64()-1); header != nil; header = chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) {
var bitmap *big.Int
istanbulExtra, err := types.ExtractIstanbulExtra(header)
if err == nil {
bitmap = istanbulExtra.AggregatedSeal.Bitmap
cache[header.Number.Uint64()] = bitmap
}
if n == header.Number.Uint64() {
return bitmap
}
}
return nil
nategraf marked this conversation as resolved.
Show resolved Hide resolved
}
}

// 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
}
}

// An EVM handler to make calls to smart contracts from within geth
type InternalEVMHandler struct {
chain ChainContext
Expand Down
Loading