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

validator set precompile #118

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
21 changes: 14 additions & 7 deletions consensus/polybft/contractsapi/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,14 @@ var (

// test smart contracts
//go:embed test-contracts/*
testContracts embed.FS
TestWriteBlockMetadata *contracts.Artifact
RootERC20 *contracts.Artifact
TestSimple *contracts.Artifact
TestRewardToken *contracts.Artifact
Wrapper *contracts.Artifact
NumberPersister *contracts.Artifact
testContracts embed.FS
TestWriteBlockMetadata *contracts.Artifact
RootERC20 *contracts.Artifact
TestSimple *contracts.Artifact
TestRewardToken *contracts.Artifact
Wrapper *contracts.Artifact
NumberPersister *contracts.Artifact
TestValidatorSetPrecompile *contracts.Artifact

contractArtifacts map[string]*contracts.Artifact
)
Expand Down Expand Up @@ -270,6 +271,11 @@ func init() {
log.Fatal(err)
}

TestValidatorSetPrecompile, err = contracts.DecodeArtifact(readTestContractContent("TestValidatorSetPrecompile.json"))
if err != nil {
log.Fatal(err)
}

StakeManager, err = contracts.DecodeArtifact([]byte(StakeManagerArtifact))
if err != nil {
log.Fatal(err)
Expand Down Expand Up @@ -381,6 +387,7 @@ func init() {
"RootERC20": RootERC20,
"TestSimple": TestSimple,
"TestRewardToken": TestRewardToken,
"TestValidatorSetPrecompile": TestValidatorSetPrecompile,
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "TestValidatorSetPrecompile",
"sourceName": "consensus/polybft/contractsapi/test-contracts/TestValidatorSetContract.sol",
"abi": [
{
"inputs":[

],
"name":"VALIDATOR_SET_PRECOMPILE",
"outputs":[
{
"internalType":"address",
"name":"",
"type":"address"
}
],
"stateMutability":"view",
"type":"function"
},
{
"inputs":[

],
"name":"VALIDATOR_SET_PRECOMPILE_GAS",
"outputs":[
{
"internalType":"uint256",
"name":"",
"type":"uint256"
}
],
"stateMutability":"view",
"type":"function"
},
{
"inputs":[

],
"name":"hasQuorum",
"outputs":[
{
"internalType":"bool",
"name":"",
"type":"bool"
}
],
"stateMutability":"view",
"type":"function"
},
{
"inputs":[

],
"name":"inc",
"outputs":[

],
"stateMutability":"nonpayable",
"type":"function"
}
],
"bytecode": "0x608060405234801561000f575f80fd5b506106508061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c8063371303c014610038578063815b4d1b14610042575b5f80fd5b610040610060565b005b61004a610251565b6040516100579190610320565b60405180910390f35b5f8061204073ffffffffffffffffffffffffffffffffffffffff166203a980336040516020016100909190610378565b6040516020818303038152906040526040516100ac91906103fd565b5f604051808303818686fa925050503d805f81146100e5576040519150601f19603f3d011682016040523d82523d5f602084013e6100ea565b606091505b509150915081801561010c57508080602001905181019061010b9190610441565b5b61014b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610142906104c6565b60405180910390fd5b5f803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff1661024d57600133908060018154018082558091505060019003905f5260205f20015f9091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060015f803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055505b5050565b5f805f61204073ffffffffffffffffffffffffffffffffffffffff166203a980600160405160200161028391906105fa565b60405160208183030381529060405260405161029f91906103fd565b5f604051808303818686fa925050503d805f81146102d8576040519150601f19603f3d011682016040523d82523d5f602084013e6102dd565b606091505b50915091508180156102ff5750808060200190518101906102fe9190610441565b5b9250505090565b5f8115159050919050565b61031a81610306565b82525050565b5f6020820190506103335f830184610311565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61036282610339565b9050919050565b61037281610358565b82525050565b5f60208201905061038b5f830184610369565b92915050565b5f81519050919050565b5f81905092915050565b5f5b838110156103c25780820151818401526020810190506103a7565b5f8484015250505050565b5f6103d782610391565b6103e1818561039b565b93506103f18185602086016103a5565b80840191505092915050565b5f61040882846103cd565b915081905092915050565b5f80fd5b61042081610306565b811461042a575f80fd5b50565b5f8151905061043b81610417565b92915050565b5f6020828403121561045657610455610413565b5b5f6104638482850161042d565b91505092915050565b5f82825260208201905092915050565b7f76616c696461746f7200000000000000000000000000000000000000000000005f82015250565b5f6104b060098361046c565b91506104bb8261047c565b602082019050919050565b5f6020820190508181035f8301526104dd816104a4565b9050919050565b5f81549050919050565b5f82825260208201905092915050565b5f819050815f5260205f209050919050565b61051981610358565b82525050565b5f61052a8383610510565b60208301905092915050565b5f815f1c9050919050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61057261056d83610536565b610541565b9050919050565b5f6105848254610560565b9050919050565b5f600182019050919050565b5f6105a1826104e4565b6105ab81856104ee565b93506105b6836104fe565b805f5b838110156105ed576105ca82610579565b6105d4888261051f565b97506105df8361058b565b9250506001810190506105b9565b5085935050505092915050565b5f6020820190508181035f8301526106128184610597565b90509291505056fea26469706673582212206efd0d43fe03760dad6d6d6027319396055df00430d6654ecf53622a2dc85a4764736f6c63430008180033",
"linkReferences": {},
"deployedLinkReferences": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
// TestValidatorSetPrecompile.sol
// Contract which testst ValidatorSet precompile
pragma solidity ^0.8.0;

contract TestValidatorSetPrecompile {
address constant VALIDATOR_SET_PRECOMPILE = 0x0000000000000000000000000000000000002040;
uint256 constant VALIDATOR_SET_PRECOMPILE_GAS = 240000;

mapping(address => bool) voteMap;
address[] votes;

modifier onlyValidator() {
(bool callSuccess, bytes memory returnData) = VALIDATOR_SET_PRECOMPILE.staticcall{
gas: VALIDATOR_SET_PRECOMPILE_GAS
}(abi.encode(msg.sender));
require(callSuccess && abi.decode(returnData, (bool)), "validator");
_;
}

function inc() public onlyValidator {
if (!voteMap[msg.sender]) {
votes.push(msg.sender);
voteMap[msg.sender] = true;
}
}

function hasQuorum() public view returns (bool) {
(bool callSuccess, bytes memory returnData) = VALIDATOR_SET_PRECOMPILE.staticcall{
gas: VALIDATOR_SET_PRECOMPILE_GAS
}(abi.encode(votes));
return callSuccess && abi.decode(returnData, (bool));
}
}
7 changes: 7 additions & 0 deletions consensus/polybft/polybft.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,9 @@ func (p *Polybft) Initialize() error {
executor: p.config.Executor,
}

// enable validatorset precompile
p.config.Executor.SetValidatorSetBackend(p)

// create bridge and consensus topics
if err = p.createTopics(); err != nil {
return fmt.Errorf("cannot create topics: %w", err)
Expand Down Expand Up @@ -754,6 +757,10 @@ func (p *Polybft) GetValidators(blockNumber uint64, parents []*types.Header) (va
return p.validatorsCache.GetSnapshot(blockNumber, parents, nil)
}

func (p *Polybft) GetValidatorsForBlock(blockNumber uint64) (validator.AccountSet, error) {
return p.validatorsCache.GetSnapshot(blockNumber, nil, nil)
}

func (p *Polybft) GetValidatorsWithTx(blockNumber uint64, parents []*types.Header,
dbTx *bolt.Tx) (validator.AccountSet, error) {
return p.validatorsCache.GetSnapshot(blockNumber, parents, dbTx)
Expand Down
16 changes: 7 additions & 9 deletions consensus/polybft/validators_snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,15 +182,13 @@ func (v *validatorsSnapshotCache) computeSnapshot(

v.logger.Trace("Compute snapshot started...", "BlockNumber", nextEpochEndBlockNumber)

if len(parents) > 0 {
for i := len(parents) - 1; i >= 0; i-- {
parentHeader := parents[i]
if parentHeader.Number == nextEpochEndBlockNumber {
v.logger.Trace("Compute snapshot. Found header in parents", "Header", parentHeader.Number)
header = parentHeader

break
}
for i := len(parents) - 1; i >= 0; i-- {
parentHeader := parents[i]
if parentHeader.Number == nextEpochEndBlockNumber {
v.logger.Trace("Compute snapshot. Found header in parents", "Header", parentHeader.Number)
header = parentHeader

break
}
}

Expand Down
4 changes: 3 additions & 1 deletion contracts/system_addresses.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,10 @@ var (

// NativeTransferPrecompile is an address of native transfer precompile
NativeTransferPrecompile = types.StringToAddress("0x2020")
// BLSAggSigsVerificationPrecompile is an address of BLS aggregated signatures verificatin precompile
// BLSAggSigsVerificationPrecompile is an address of BLS aggregated signatures verification precompile
BLSAggSigsVerificationPrecompile = types.StringToAddress("0x2030")
// ValidatorSetPrecompile is an address of precompile which provides some validatorSet functionalities
ValidatorSetPrecompile = types.StringToAddress("0x2040")
// ConsolePrecompile is and address of Hardhat console precompile
ConsolePrecompile = types.StringToAddress("0x000000000000000000636F6e736F6c652e6c6f67")
// AllowListContractsAddr is the address of the contract deployer allow list
Expand Down
116 changes: 116 additions & 0 deletions e2e-polybft/e2e/consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -823,3 +823,119 @@ func TestE2E_Deploy_Nested_Contract(t *testing.T) {

require.Equal(t, numberToPersist, parsedResponse)
}

func TestE2E_TestValidatorSetPrecompile(t *testing.T) {
var (
premineBalance = ethgo.Ether(2e6) // 2M native tokens (so that we have enough balance to fund new validator)
stakeAmount = ethgo.Ether(500)
)

admin, err := wallet.GenerateKey()
require.NoError(t, err)

dummyKey, err := wallet.GenerateKey()
require.NoError(t, err)

// start cluster with 'validatorSize' validators
cluster := framework.NewTestCluster(t, 4,
framework.WithBladeAdmin(admin.Address().String()),
framework.WithSecretsCallback(func(addresses []types.Address, config *framework.TestClusterConfig) {
for _, a := range addresses {
config.Premine = append(config.Premine, fmt.Sprintf("%s:%s", a, premineBalance))
config.StakeAmounts = append(config.StakeAmounts, stakeAmount)
}

config.Premine = append(config.Premine, fmt.Sprintf("%s:%s", dummyKey.Address(), premineBalance))
}),
)

defer cluster.Stop()

cluster.WaitForReady(t)

validatorKeys := make([]*wallet.Key, len(cluster.Servers))

for i, s := range cluster.Servers {
voterAcc, err := validatorHelper.GetAccountFromDir(s.DataDir())
require.NoError(t, err)

validatorKeys[i] = voterAcc.Ecdsa
}

txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithClient(cluster.Servers[0].JSONRPC()))
require.NoError(t, err)

// deploy contract
receipt, err := txRelayer.SendTransaction(
&ethgo.Transaction{
To: nil,
Input: contractsapi.TestValidatorSetPrecompile.Bytecode,
},
admin)
require.NoError(t, err)

validatorSetPrecompileTestAddr := receipt.ContractAddress

hasQuorum := func() bool {
t.Helper()

hasQuorumFn := contractsapi.TestValidatorSetPrecompile.Abi.GetMethod("hasQuorum")

hasQuorumFnBytes, err := hasQuorumFn.Encode([]interface{}{})
require.NoError(t, err)

response, err := txRelayer.Call(ethgo.ZeroAddress, validatorSetPrecompileTestAddr, hasQuorumFnBytes)
require.NoError(t, err)

return response == "0x0000000000000000000000000000000000000000000000000000000000000001"
}

sendIncTx := func(validatorID int) {
igorcrevar marked this conversation as resolved.
Show resolved Hide resolved
t.Helper()

isValidator := validatorID >= 0 && validatorID < len(validatorKeys)
incFn := contractsapi.TestValidatorSetPrecompile.Abi.GetMethod("inc")

incFnBytes, err := incFn.Encode([]interface{}{})
require.NoError(t, err)

var key *wallet.Key
if isValidator {
key = validatorKeys[validatorID]
} else {
key = dummyKey
}

txn := &ethgo.Transaction{
From: key.Address(),
To: &validatorSetPrecompileTestAddr,
Input: incFnBytes,
}

receipt, err = txRelayer.SendTransaction(txn, key)

if isValidator {
require.NoError(t, err)
require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status)
} else {
require.ErrorContains(t, err, "unable to apply transaction even for the highest gas limit")
}
}

require.False(t, hasQuorum())

sendIncTx(0)
require.False(t, hasQuorum())

sendIncTx(1)
require.False(t, hasQuorum())

sendIncTx(1)
require.False(t, hasQuorum())

sendIncTx(-1) // non validator
require.False(t, hasQuorum())

sendIncTx(3)
require.True(t, hasQuorum())
}
2 changes: 1 addition & 1 deletion helper/predeployment/predeployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func getPredeployAccount(address types.Address, input []byte,
config := chain.AllForksEnabled.At(0)

// Create a transition
transition := state.NewTransition(hclog.NewNullLogger(), config, snapshot, radix)
transition := state.NewTransition(hclog.NewNullLogger(), config, snapshot, radix, nil)
transition.ContextPtr().ChainID = chainID

// Run the transition through the EVM
Expand Down
17 changes: 13 additions & 4 deletions state/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ type Executor struct {

PostHook func(txn *Transition)
GenesisPostHook func(*Transition) error

// this value should be set if we want to enable validator set precompile
// note that this precompile WONT be enabled for WriteGenesis
validatorSetBackend precompiled.ValidatorSetPrecompileBackend
}

// NewExecutor creates a new executor
Expand Down Expand Up @@ -81,7 +85,7 @@ func (e *Executor) WriteGenesis(
ChainID: e.config.ChainID,
}

transition := NewTransition(e.logger, config, snap, txn)
transition := NewTransition(e.logger, config, snap, txn, nil)
transition.ctx = env

for addr, account := range alloc {
Expand Down Expand Up @@ -199,7 +203,7 @@ func (e *Executor) BeginTxn(
BurnContract: burnContract,
}

t := NewTransition(e.logger, forkConfig, snap, newTxn)
t := NewTransition(e.logger, forkConfig, snap, newTxn, e.validatorSetBackend)
t.PostHook = e.PostHook
t.getHash = e.GetHash(header)
t.ctx = txCtx
Expand Down Expand Up @@ -235,6 +239,10 @@ func (e *Executor) BeginTxn(
return t, nil
}

func (e *Executor) SetValidatorSetBackend(validatorSetBackend precompiled.ValidatorSetPrecompileBackend) {
e.validatorSetBackend = validatorSetBackend
}

type Transition struct {
logger hclog.Logger

Expand Down Expand Up @@ -272,14 +280,15 @@ type Transition struct {
accessList *runtime.AccessList
}

func NewTransition(logger hclog.Logger, config chain.ForksInTime, snap Snapshot, radix *Txn) *Transition {
func NewTransition(logger hclog.Logger, config chain.ForksInTime,
snap Snapshot, radix *Txn, validatorSetBackend precompiled.ValidatorSetPrecompileBackend) *Transition {
return &Transition{
logger: logger,
config: config,
state: radix,
snap: snap,
evm: evm.NewEVM(),
precompiles: precompiled.NewPrecompiled(),
precompiles: precompiled.NewPrecompiled(validatorSetBackend),
journal: &runtime.Journal{},
accessList: runtime.NewAccessList(),
}
Expand Down
Loading
Loading