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

Support validator key rotation #575

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
50d3fbe
Most tests passing
Oct 1, 2019
065fda9
Merge branch 'master' into asaj/pos
Oct 3, 2019
22dda1a
compiling
Oct 4, 2019
fa7795c
Validator set changing again
Oct 5, 2019
a6c6f09
Epoch payments and rewards appear to be working
Oct 6, 2019
cc81efe
More work
Oct 7, 2019
5d1c3e9
Merge branch 'master' into asaj/pos-2
Oct 7, 2019
1fdc834
More work
Oct 8, 2019
8c4b495
Re-add epoch size precompile
Oct 8, 2019
c8c46c4
Governance end-to-end tests working again
Oct 8, 2019
414b92c
Governance end-to-end test passing
Oct 10, 2019
9de8f62
Merge master
Oct 11, 2019
30c9a35
Remove todo
Oct 11, 2019
0ec8374
Merge master
Oct 16, 2019
4cebe48
Merge asaj/pos
Oct 18, 2019
299d865
Merge master
Oct 18, 2019
309c953
Address comments
Oct 18, 2019
625babd
Fix failing tests
Oct 18, 2019
d065570
Address comments
Oct 22, 2019
5e07ae7
Address comments
Oct 23, 2019
5b85b28
Add first pass at epoch rewards
Oct 24, 2019
5800322
Merge master
Oct 30, 2019
1181d3e
Merge branch 'master' into asaj/pos-4
Oct 31, 2019
1b288a0
Allow reverting of multiple contract_comm calls
Nov 1, 2019
fe737aa
Adjust target voting yield
Nov 1, 2019
b3299b2
Merge master
Nov 7, 2019
bd12c2c
Update contract_comm to work with key rotation
Nov 8, 2019
ace53ba
Merge master
Nov 8, 2019
c3ce186
Key rotation working
Nov 10, 2019
f3f3071
Merge branch 'master' into asaj/key-rotation
Nov 10, 2019
afafee0
Merge branch 'asaj/pos-4' into asaj/key-rotation
Nov 10, 2019
748a726
Cleanup
Nov 10, 2019
dbc11f2
Update end-to-end test name
Nov 10, 2019
3e296d9
Merge branch 'asaj/pos-4' into asaj/key-rotation
Nov 10, 2019
bf8141f
Merge branch 'master' into asaj/pos-4
Nov 11, 2019
a1b4226
Merge branch 'asaj/pos-4' into asaj/key-rotation
Nov 12, 2019
2779b63
Changes
Nov 12, 2019
28c8881
Increase tx pool max tx data size
Nov 12, 2019
81863f0
WIP
Nov 12, 2019
ed4a24e
Merge master
Nov 12, 2019
a813f3b
end-to-end governance test passing
Nov 12, 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
4 changes: 2 additions & 2 deletions consensus/istanbul/backend/announce.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (sb *Backend) sendAnnounceMsgs() {
sb.announceWg.Add(1)
defer sb.announceWg.Done()

ticker := time.NewTicker(time.Minute)
ticker := time.NewTicker(time.Second)

for {
select {
Expand Down Expand Up @@ -337,7 +337,7 @@ func (sb *Backend) handleIstAnnounce(payload []byte) error {
// If we gossiped this address/enodeURL within the last 60 seconds, then don't regossip
sb.lastAnnounceGossipedMu.RLock()
if lastGossipTs, ok := sb.lastAnnounceGossiped[msg.Address]; ok {
if lastGossipTs.enodeURL == enodeURL && time.Since(lastGossipTs.timestamp) < time.Minute {
if lastGossipTs.enodeURL == enodeURL && time.Since(lastGossipTs.timestamp) < time.Second {
sb.logger.Trace("Already regossiped the msg within the last minute, so not regossiping.", "AnnounceMsg", msg)
sb.lastAnnounceGossipedMu.RUnlock()
return nil
Expand Down
1 change: 1 addition & 0 deletions consensus/istanbul/backend/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ func (sb *Backend) Finalize(chain consensus.ChainReader, header *types.Header, s
if istanbul.IsLastBlockOfEpoch(header.Number.Uint64(), sb.config.Epoch) {
snapshot = state.Snapshot()
err = sb.distributeEpochPaymentsAndRewards(header, state)
log.Info("Distributed epoch payments and rewards", "err", err)
if err != nil {
state.RevertToSnapshot(snapshot)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,8 @@ func NewValidatorEnodeTable() *ValidatorEnodeTable {

func (vet *ValidatorEnodeTable) String() string {
var b strings.Builder
b.WriteString("ValEnodeTable:")
for _, valEnode := range vet.valEnodeTable {
fmt.Fprintf(&b, "%s\t", valEnode.String())
fmt.Fprintf(&b, "%s\n", valEnode.String())
}
return b.String()
}
Expand Down
2 changes: 1 addition & 1 deletion consensus/istanbul/core/persistent_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func writeToDisk(filePath string, data []byte) error {
if err4 != nil {
return err4
}
log.Debug("Syncing dir %s\n", fpDir.Name())
log.Debug("Syncing dir", "dir", fpDir.Name())
err5 := fpDir.Sync()
if err5 != nil {
return err5
Expand Down
1 change: 1 addition & 0 deletions consensus/istanbul/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ func GetEpochLastBlockNumber(epochNumber uint64, epochSize uint64) uint64 {
return firstBlockNum + (epochSize - 1)
}

// TODO(asa): Make this return a diff if at least one of the address/blskey changes. Right now, only returns diff if address changes.
func ValidatorSetDiff(oldValSet []ValidatorData, newValSet []ValidatorData) ([]ValidatorData, *big.Int) {
valSetMap := make(map[common.Address]bool)
oldValSetMap := make(map[common.Address]int)
Expand Down
4 changes: 2 additions & 2 deletions contract_comm/election/election.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import (
const electionABIString string = `[
{"constant": true,
"inputs": [],
"name": "electValidators",
"name": "electValidatorSigners",
"outputs": [
{
"name": "",
Expand Down Expand Up @@ -118,7 +118,7 @@ var electionABI, _ = abi.JSON(strings.NewReader(electionABIString))
func GetElectedValidators(header *types.Header, state vm.StateDB) ([]common.Address, error) {
var newValSet []common.Address
// Get the new epoch's validator set
_, err := contract_comm.MakeStaticCall(params.ElectionRegistryId, electionABI, "electValidators", []interface{}{}, &newValSet, params.MaxGasForElectValidators, header, state)
_, err := contract_comm.MakeStaticCall(params.ElectionRegistryId, electionABI, "electValidatorSigners", []interface{}{}, &newValSet, params.MaxGasForElectValidators, header, state)
if err != nil {
return nil, err
}
Expand Down
72 changes: 29 additions & 43 deletions contract_comm/validators/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,41 +32,33 @@ import (

// This is taken from celo-monorepo/packages/protocol/build/<env>/contracts/Validators.json
const validatorsABIString string = `[
{
"constant": true,
"inputs": [],
"name": "getRegisteredValidators",
"outputs": [
{
"constant": true,
"inputs": [],
"name": "getRegisteredValidatorSigners",
"outputs": [
{
"name": "",
"type": "address[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"name": "",
"type": "address[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "account",
"name": "signer",
"type": "address"
}
],
"name": "getValidator",
"name": "getValidatorBlsKeyFromSigner",
"outputs": [
{
"name": "publicKeysData",
"name": "blsKey",
"type": "bytes"
},
{
"name": "affiliation",
"type": "address"
},
{
"name": "score",
"type": "uint256"
}
],
"payable": false,
Expand All @@ -85,7 +77,7 @@ const validatorsABIString string = `[
"type": "uint256"
}
],
"name": "distributeEpochPayment",
"name": "distributeEpochPaymentsFromSigner",
"outputs": [
{
"name": "",
Expand All @@ -108,7 +100,7 @@ const validatorsABIString string = `[
"type": "uint256"
}
],
"name": "updateValidatorScore",
"name": "updateValidatorScoreFromSigner",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
Expand All @@ -122,7 +114,7 @@ const validatorsABIString string = `[
"type": "address"
}
],
"name": "getMembershipInLastEpoch",
"name": "getMembershipInLastEpochFromSigner",
"outputs": [
{
"name": "",
Expand All @@ -141,7 +133,7 @@ func RetrieveRegisteredValidators(header *types.Header, state vm.StateDB) ([]com
var regVals []common.Address

// Get the new epoch's validator set
if _, err := contract_comm.MakeStaticCall(params.ValidatorsRegistryId, validatorsABI, "getRegisteredValidators", []interface{}{}, &regVals, params.MaxGasForGetRegisteredValidators, header, state); err != nil {
if _, err := contract_comm.MakeStaticCall(params.ValidatorsRegistryId, validatorsABI, "getRegisteredValidatorSigners", []interface{}{}, &regVals, params.MaxGasForGetRegisteredValidators, header, state); err != nil {
return nil, err
}

Expand All @@ -151,23 +143,17 @@ func RetrieveRegisteredValidators(header *types.Header, state vm.StateDB) ([]com
func GetValidatorData(header *types.Header, state vm.StateDB, validatorAddresses []common.Address) ([]istanbul.ValidatorData, error) {
var validatorData []istanbul.ValidatorData
for _, addr := range validatorAddresses {
validator := struct {
PublicKeysData []byte
Affiliation common.Address
Score *big.Int
}{}
_, err := contract_comm.MakeStaticCall(params.ValidatorsRegistryId, validatorsABI, "getValidator", []interface{}{addr}, &validator, params.MaxGasForGetValidator, header, state)
var blsKey []byte
_, err := contract_comm.MakeStaticCall(params.ValidatorsRegistryId, validatorsABI, "getValidatorBlsKeyFromSigner", []interface{}{addr}, &blsKey, params.MaxGasForGetValidator, header, state)
if err != nil {
return nil, err
}
expectedLength := 64 + blscrypto.PUBLICKEYBYTES + blscrypto.SIGNATUREBYTES
if len(validator.PublicKeysData) != expectedLength {
return nil, fmt.Errorf("length of publicKeysData incorrect. Expected %d, got %d", expectedLength, len(validator.PublicKeysData))
if len(blsKey) != blscrypto.PUBLICKEYBYTES {
return nil, fmt.Errorf("length of BLS key incorrect. Expected %d, got %d", blscrypto.PUBLICKEYBYTES, len(blsKey))
}
blsPublicKey := validator.PublicKeysData[64 : 64+blscrypto.PUBLICKEYBYTES]
validatorData = append(validatorData, istanbul.ValidatorData{
addr,
blsPublicKey,
blsKey,
})
}
return validatorData, nil
Expand All @@ -177,7 +163,7 @@ func UpdateValidatorScore(header *types.Header, state vm.StateDB, address common
_, err := contract_comm.MakeCall(
params.ValidatorsRegistryId,
validatorsABI,
"updateValidatorScore",
"updateValidatorScoreFromSigner",
[]interface{}{address, uptime},
nil,
params.MaxGasForUpdateValidatorScore,
Expand All @@ -194,7 +180,7 @@ func DistributeEpochPayment(header *types.Header, state vm.StateDB, address comm
_, err := contract_comm.MakeCall(
params.ValidatorsRegistryId,
validatorsABI,
"distributeEpochPayment",
"distributeEpochPaymentsFromSigner",
[]interface{}{address, maxPayment},
&epochPayment,
params.MaxGasForDistributeEpochPayment,
Expand All @@ -208,7 +194,7 @@ func DistributeEpochPayment(header *types.Header, state vm.StateDB, address comm

func GetMembershipInLastEpoch(header *types.Header, state vm.StateDB, validator common.Address) (common.Address, error) {
var group common.Address
_, err := contract_comm.MakeStaticCall(params.ValidatorsRegistryId, validatorsABI, "getMembershipInLastEpoch", []interface{}{validator}, &group, params.MaxGasForGetMembershipInLastEpoch, header, state)
_, err := contract_comm.MakeStaticCall(params.ValidatorsRegistryId, validatorsABI, "getMembershipInLastEpochFromSigner", []interface{}{validator}, &group, params.MaxGasForGetMembershipInLastEpoch, header, state)
if err != nil {
return common.ZeroAddress, err
}
Expand Down
4 changes: 2 additions & 2 deletions core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,8 +605,8 @@ func (pool *TxPool) local() map[common.Address]types.Transactions {
// validateTx checks whether a transaction is valid according to the consensus
// rules and adheres to some heuristic limits of the local node (price and size).
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// Heuristic limit, reject transactions over 32KB to prevent DOS attacks
if tx.Size() > 32*1024 {
// Heuristic limit, reject transactions over MaxCodeSize to prevent DOS attacks
if tx.Size() > params.MaxCodeSize {
return ErrOversizedData
}
// Transactions can't be negative. This may never happen using RLP decoded
Expand Down
85 changes: 62 additions & 23 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{
}

var CeloPrecompiledContractsAddressOffset = byte(0xff)
var ecrecoverPublicKeyAddress = common.BytesToAddress(append([]byte{0}, (CeloPrecompiledContractsAddressOffset - 1)))
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)))
Expand All @@ -72,12 +73,13 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{8}): &bn256Pairing{},

// Celo Precompiled Contracts
transferAddress: &transfer{},
fractionMulExpAddress: &fractionMulExp{},
proofOfPossessionAddress: &proofOfPossession{},
getValidatorAddress: &getValidator{},
numberValidatorsAddress: &numberValidators{},
epochSizeAddress: &epochSize{},
ecrecoverPublicKeyAddress: &ecrecoverPublicKey{},
transferAddress: &transfer{},
fractionMulExpAddress: &fractionMulExp{},
proofOfPossessionAddress: &proofOfPossession{},
getValidatorAddress: &getValidator{},
numberValidatorsAddress: &numberValidators{},
epochSizeAddress: &epochSize{},
}

// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
Expand All @@ -97,42 +99,73 @@ func debitRequiredGas(p PrecompiledContract, input []byte, gas uint64) (uint64,
return gas - requiredGas, nil
}

// ECRECOVER implemented as a native contract.
func ecrecoverHelper(input []byte) ([]byte, error) {
const ecRecoverInputLength = 128

input = common.RightPadBytes(input, ecRecoverInputLength)
// "input" is (hash, v, r, s), each 32 bytes
// but for ecrecover we want (r, s, v)

r := new(big.Int).SetBytes(input[64:96])
s := new(big.Int).SetBytes(input[96:128])
v := input[63] - 27

// tighter sig s values input homestead only apply to tx sigs
if !allZero(input[32:63]) || !crypto.ValidateSignatureValues(v, r, s, false) {
return nil, nil
}
// v needs to be at the end for libsecp256k1
return crypto.Ecrecover(input[:32], append(input[64:128], v))
}

// ECRECOVER implemented as a native contract. This variant returns the address.
type ecrecover struct{}

func (c *ecrecover) RequiredGas(input []byte) uint64 {
return params.EcrecoverGas
}

func (c *ecrecover) Run(input []byte, caller common.Address, evm *EVM, gas uint64) ([]byte, uint64, error) {
log.Info("called ecrecover", "intpu", hexutil.Encode(input))
gas, err := debitRequiredGas(c, input, gas)
if err != nil {
return nil, gas, err
}

const ecRecoverInputLength = 128
pubKey, err := ecrecoverHelper(input)
log.Info("recovered public key", "pubkey", hexutil.Encode(pubKey))
// make sure the public key is a valid one
if err != nil || pubKey == nil {
return nil, gas, nil
}

input = common.RightPadBytes(input, ecRecoverInputLength)
// "input" is (hash, v, r, s), each 32 bytes
// but for ecrecover we want (r, s, v)
// the first byte of pubkey is bitcoin heritage
return common.LeftPadBytes(crypto.Keccak256(pubKey[1:])[12:], 32), gas, nil
}

r := new(big.Int).SetBytes(input[64:96])
s := new(big.Int).SetBytes(input[96:128])
v := input[63] - 27
// ECRECOVER implemented as a native contract. This variant returns the full public key.
type ecrecoverPublicKey struct{}

// tighter sig s values input homestead only apply to tx sigs
if !allZero(input[32:63]) || !crypto.ValidateSignatureValues(v, r, s, false) {
return nil, gas, nil
func (c *ecrecoverPublicKey) RequiredGas(input []byte) uint64 {
return params.EcrecoverGas
}

func (c *ecrecoverPublicKey) Run(input []byte, caller common.Address, evm *EVM, gas uint64) ([]byte, uint64, error) {
log.Info("called ecrecover publickey", "intpu", hexutil.Encode(input))
gas, err := debitRequiredGas(c, input, gas)
if err != nil {
return nil, gas, err
}
// v needs to be at the end for libsecp256k1
pubKey, err := crypto.Ecrecover(input[:32], append(input[64:128], v))

pubKey, err := ecrecoverHelper(input)
// make sure the public key is a valid one
if err != nil {
if err != nil || pubKey == nil {
return nil, gas, nil
}
log.Info("recovered public key", "pubkey", hexutil.Encode(pubKey))

// the first byte of pubkey is bitcoin heritage
return common.LeftPadBytes(crypto.Keccak256(pubKey[1:])[12:], 32), gas, nil
return pubKey[1:], gas, nil
}

// SHA256 implemented as a native contract.
Expand Down Expand Up @@ -555,36 +588,42 @@ func (c *proofOfPossession) RequiredGas(input []byte) uint64 {
}

func (c *proofOfPossession) Run(input []byte, caller common.Address, evm *EVM, gas uint64) ([]byte, uint64, error) {
log.Info("Called proofofpossession")
gas, err := debitRequiredGas(c, input, gas)
if err != nil {
return nil, gas, err
}

// input is comprised of 2 arguments:
// input is comprised of 3 arguments:
// address: 20 bytes, an address used to generate the proof-of-possession
// publicKey: 48 bytes, representing the public key (defined as a const in bls package)
// signature: 96 bytes, representing the signature (defined as a const in bls package)
// signature: 96 bytes, representing the signature on `address` (defined as a const in bls package)
// the total length of input required is the sum of these constants
if len(input) != common.AddressLength+blscrypto.PUBLICKEYBYTES+blscrypto.SIGNATUREBYTES {
log.Error("ProofOfPoss", "len1", len(input), "len2", common.AddressLength+blscrypto.PUBLICKEYBYTES+blscrypto.SIGNATUREBYTES)
return nil, gas, ErrInputLength
}
addressBytes := input[:common.AddressLength]

publicKeyBytes := input[common.AddressLength : common.AddressLength+blscrypto.PUBLICKEYBYTES]
publicKey, err := bls.DeserializePublicKey(publicKeyBytes)
if err != nil {
log.Error("error deserializing public key", "err", err)
return nil, gas, err
}
defer publicKey.Destroy()

signatureBytes := input[common.AddressLength+blscrypto.PUBLICKEYBYTES : common.AddressLength+blscrypto.PUBLICKEYBYTES+blscrypto.SIGNATUREBYTES]
signature, err := bls.DeserializeSignature(signatureBytes)
if err != nil {
log.Error("error deserializing signature", "err", err)
return nil, gas, err
}
defer signature.Destroy()

err = publicKey.VerifyPoP(addressBytes, signature)
if err != nil {
log.Error("error verifying pop", "err", err)
return nil, gas, err
}

Expand Down