Skip to content

Commit

Permalink
Benchmark committee selection (autonity#892)
Browse files Browse the repository at this point in the history
* wip

Signed-off-by: yazzaoui <[email protected]>

* moved hardcoded address to one place

* bindings for helper contract for contract tests in go

* applyStakingOperations for testing

* benchmark computeCommittee and refactor

* generated bindings

* removed unnecessary code

* improve precision

* return left over gas

* benchmark gas used

* lint fix

* update makefile

* updated hardcoded addresses

* do the first call before benchmark

* error check

* bindings

---------

Signed-off-by: yazzaoui <[email protected]>
Co-authored-by: yazzaoui <[email protected]>
  • Loading branch information
tbssajal and yazzaoui authored Jan 15, 2024
1 parent 94f95a9 commit ee8c11f
Show file tree
Hide file tree
Showing 9 changed files with 3,212 additions and 9 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ contracts: $(SOLC_BINARY) $(GOBINDATA_BINARY) $(CONTRACTS_DIR)/*.sol $(ABIGEN_BI
@$(call gen-contract,,Oracle)
@$(call gen-contract,,AutonityUpgradeTest)
@$(call gen-contract,,Accountability)
@$(call gen-contract,,AutonityTest)
@$(call gen-contract,,AccountabilityTest)
@$(call gen-contract,asm/,ACU)
@$(call gen-contract,asm/,SupplyControl)
@$(call gen-contract,asm/,Stabilization)
Expand All @@ -97,7 +99,6 @@ contracts: $(SOLC_BINARY) $(GOBINDATA_BINARY) $(CONTRACTS_DIR)/*.sol $(ABIGEN_BI
$(ABIGEN_BINARY) --pkg autonity --solc $(SOLC_BINARY) --sol $(CONTRACTS_DIR)/bindings.sol --out ./autonity/bindings.go



$(SOLC_BINARY):
mkdir -p $(BINDIR)
wget -O $(SOLC_BINARY) https://github.com/ethereum/solidity/releases/download/v$(SOLC_VERSION)/solc-static-linux
Expand Down
3 changes: 2 additions & 1 deletion autonity/autonity.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,8 @@ func (c *AutonityContract) upgradeAutonityContract(statedb *state.StateDB, heade
}

func (c *AutonityContract) CallContractFunc(statedb *state.StateDB, header *types.Header, packedArgs []byte) ([]byte, error) {
return c.EVMContract.CallContractFunc(statedb, header, AutonityContractAddress, packedArgs)
packedResult, _, err := c.EVMContract.CallContractFunc(statedb, header, AutonityContractAddress, packedArgs)
return packedResult, err
}

func (c *AutonityContract) CallContractFuncAs(statedb *state.StateDB, header *types.Header, origin common.Address, packedArgs []byte) ([]byte, error) {
Expand Down
217 changes: 216 additions & 1 deletion autonity/autonity_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,61 @@
package autonity

import (
"crypto/ecdsa"
"errors"
"log"
"math/big"
"math/rand"
"sort"
"testing"

"github.com/stretchr/testify/require"

"github.com/autonity/autonity/accounts/abi"
"github.com/autonity/autonity/common"
"github.com/autonity/autonity/common/math"
"github.com/autonity/autonity/core/rawdb"
"github.com/autonity/autonity/core/state"
"github.com/autonity/autonity/core/types"
"github.com/autonity/autonity/core/vm"
"github.com/autonity/autonity/crypto"
"github.com/stretchr/testify/require"
"github.com/autonity/autonity/params"
"github.com/autonity/autonity/params/generated"
)

func BenchmarkComputeCommittee(b *testing.B) {
// Deploy contract
stateDb, evm, evmContract, err := initalizeEvm(&generated.AutonityTestAbi)
require.NoError(b, err)

validatorCount := 100000
validators, _, err := randomValidators(validatorCount, 30)
require.NoError(b, err)
contractAbi := &generated.AutonityTestAbi
deployer := common.Address{}
committeeSize := 100
contractAddress, err := deployContract(contractAbi, generated.AutonityTestBytecode, deployer, validators, evm, committeeSize)
require.NoError(b, err)
var header *types.Header
err = callContractFunction(evmContract, contractAddress, stateDb, header, contractAbi, "applyStakingOperations")
require.NoError(b, err)
packedArgs, err := contractAbi.Pack("computeCommittee")
require.NoError(b, err)
// the first run is different from the rest, because the db is empty and the function will write committee members in db
_, _, err = evmContract.CallContractFunc(stateDb, header, contractAddress, packedArgs)
require.NoError(b, err)
gas := uint64(math.MaxUint64)
var gasUsed uint64

b.ResetTimer()
for i := 0; i < b.N; i++ {
_, gasLeft, err := evmContract.CallContractFunc(stateDb, header, contractAddress, packedArgs)
require.NoError(b, err)
gasUsed += gas - gasLeft
}
b.Log(1.0 * gasUsed / uint64(b.N))
}

func TestElectProposer(t *testing.T) {
height := uint64(9999)
samePowers := []int{100, 100, 100, 100}
Expand Down Expand Up @@ -121,3 +164,175 @@ func generateCommittee(powers []int) types.Committee {
sort.Sort(vals)
return vals
}

func initalizeEvm(abi *abi.ABI) (*state.StateDB, *vm.EVM, *EVMContract, error) {
ethDb := rawdb.NewMemoryDatabase()
db := state.NewDatabase(ethDb)
stateDB, err := state.New(common.Hash{}, db, nil)
if err != nil {
return new(state.StateDB), new(vm.EVM), new(EVMContract), err
}
evm := createTestVM(stateDB)
evmContract := NewEVMContract(testEVMProvider(), abi, ethDb, params.TestChainConfig)
return stateDB, evm, evmContract, nil
}

func deployContract(
abi *abi.ABI, byteCode []byte, deployer common.Address, validators []params.Validator, evm *vm.EVM, committeeSize int,
) (common.Address, error) {
gas := uint64(math.MaxUint64)
value := common.Big0
contractConfig := autonityTestConfig()
contractConfig.Protocol.CommitteeSize = big.NewInt(int64(committeeSize))
args, err := abi.Pack("", validators, contractConfig)
if err != nil {
return common.Address{}, err
}
data := append(byteCode, args...)
_, contractAddress, _, err := evm.Create(vm.AccountRef(deployer), data, gas, value)
if err != nil {
return common.Address{}, err
}
return contractAddress, nil
}

// Packs the args and then calls the function
// can also return result if needed
func callContractFunction(
evmContract *EVMContract, contractAddress common.Address, stateDb *state.StateDB, header *types.Header, abi *abi.ABI,
methodName string, args ...interface{},
) error {
argsPacked, err := abi.Pack(methodName, args...)
if err != nil {
return err
}
_, _, err = evmContract.CallContractFunc(stateDb, header, contractAddress, argsPacked)
return err
}

func randomValidators(count int, randomPercentage int) ([]params.Validator, []*ecdsa.PrivateKey, error) {
if count == 0 {
return []params.Validator{}, []*ecdsa.PrivateKey{}, nil
}

bondedStake := make([]int64, count)
for i := 0; i < count; i++ {
bondedStake[i] = int64(rand.Uint64() >> 1)
}
if randomPercentage < 100 {
sort.SliceStable(bondedStake, func(i, j int) bool {
return bondedStake[i] > bondedStake[j]
})
if bondedStake[0] < bondedStake[1] {
return []params.Validator{}, []*ecdsa.PrivateKey{}, errors.New("Not sorted")
}
}

validatorList := make([]params.Validator, count)
privateKeyList := make([]*ecdsa.PrivateKey, count)
for i := 0; i < count; i++ {
var privateKey *ecdsa.PrivateKey
var err error
for {
privateKey, err = crypto.GenerateKey()
if err == nil {
break
}
}
privateKeyList[i] = privateKey
publicKey := privateKey.PublicKey
enode := "enode://" + string(crypto.PubECDSAToHex(&publicKey)[2:]) + "@3.209.45.79:30303"
address := crypto.PubkeyToAddress(publicKey)
validatorList[i] = params.Validator{
Treasury: address,
Enode: enode,
BondedStake: big.NewInt(bondedStake[i]),
}
err = validatorList[i].Validate()
if err != nil {
return []params.Validator{}, []*ecdsa.PrivateKey{}, err
}
}

if randomPercentage == 0 || randomPercentage == 100 {
return validatorList, privateKeyList, nil
}

randomValidatorCount := count * randomPercentage / 100
randomIndex := make(map[uint32]bool)
randomIndex[0] = true
for i := 0; i < randomValidatorCount; i++ {
var idx uint32
for {
idx = rand.Uint32() % uint32(count)
_, ok := randomIndex[idx]
if !ok {
break
}
}

stake := validatorList[idx-1].BondedStake
validatorList[idx].BondedStake = new(big.Int).Add(stake, big.NewInt(int64(rand.Uint64()>>1)))
randomIndex[idx] = true
}
return validatorList, privateKeyList, nil
}

func autonityTestConfig() AutonityConfig {
config := AutonityConfig{
Policy: AutonityPolicy{
TreasuryFee: new(big.Int).SetUint64(params.TestAutonityContractConfig.TreasuryFee),
MinBaseFee: new(big.Int).SetUint64(params.TestAutonityContractConfig.MinBaseFee),
DelegationRate: new(big.Int).SetUint64(params.TestAutonityContractConfig.DelegationRate),
UnbondingPeriod: new(big.Int).SetUint64(params.TestAutonityContractConfig.UnbondingPeriod),
TreasuryAccount: params.TestAutonityContractConfig.Operator,
},
Contracts: AutonityContracts{
AccountabilityContract: AccountabilityContractAddress,
OracleContract: OracleContractAddress,
AcuContract: ACUContractAddress,
SupplyControlContract: SupplyControlContractAddress,
StabilizationContract: StabilizationContractAddress,
},
Protocol: AutonityProtocol{
OperatorAccount: params.TestAutonityContractConfig.Operator,
EpochPeriod: new(big.Int).SetUint64(params.TestAutonityContractConfig.EpochPeriod),
BlockPeriod: new(big.Int).SetUint64(params.TestAutonityContractConfig.BlockPeriod),
CommitteeSize: new(big.Int).SetUint64(params.TestAutonityContractConfig.MaxCommitteeSize),
},
ContractVersion: big.NewInt(1),
}
return config
}

func createTestVM(state vm.StateDB) *vm.EVM {
vmBlockContext := vm.BlockContext{
Transfer: func(vm.StateDB, common.Address, common.Address, *big.Int) {},
CanTransfer: func(vm.StateDB, common.Address, *big.Int) bool { return true },
BlockNumber: common.Big0,
}

txContext := vm.TxContext{
Origin: common.Address{},
GasPrice: common.Big0,
}

evm := vm.NewEVM(vmBlockContext, txContext, state, params.TestChainConfig, vm.Config{})
return evm
}

func testEVMProvider() func(header *types.Header, origin common.Address, statedb *state.StateDB) *vm.EVM {
return func(header *types.Header, origin common.Address, statedb *state.StateDB) *vm.EVM {
vmBlockContext := vm.BlockContext{
Transfer: func(vm.StateDB, common.Address, common.Address, *big.Int) {},
CanTransfer: func(vm.StateDB, common.Address, *big.Int) bool { return true },
BlockNumber: common.Big0,
}
txContext := vm.TxContext{
Origin: common.Address{},
GasPrice: common.Big0,
}
evm := vm.NewEVM(vmBlockContext, txContext, statedb, params.TestChainConfig, vm.Config{})
return evm
}
}
5 changes: 2 additions & 3 deletions autonity/calls.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,11 +351,10 @@ func (c *AutonityContract) FinalizeInitialization(header *types.Header, statedb
// packed result. If there is an error making the evm call it will be returned.
// Callers should use the autonity contract ABI to pack and unpack the args and
// result.
func (c *EVMContract) CallContractFunc(statedb *state.StateDB, header *types.Header, contractAddress common.Address, packedArgs []byte) ([]byte, error) {
func (c *EVMContract) CallContractFunc(statedb *state.StateDB, header *types.Header, contractAddress common.Address, packedArgs []byte) ([]byte, uint64, error) {
gas := uint64(math.MaxUint64)
evm := c.evmProvider(header, DeployerAddress, statedb)
packedResult, _, err := evm.Call(vm.AccountRef(DeployerAddress), contractAddress, packedArgs, gas, new(big.Int))
return packedResult, err
return evm.Call(vm.AccountRef(DeployerAddress), contractAddress, packedArgs, gas, new(big.Int))
}

func (c *EVMContract) CallContractFuncAs(statedb *state.StateDB, header *types.Header, contractAddress common.Address, origin common.Address, packedArgs []byte) ([]byte, error) {
Expand Down
4 changes: 4 additions & 0 deletions autonity/solidity/contracts/AutonityTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ contract AutonityTest is Autonity {
Autonity._applyNewCommissionRates();
}

function applyStakingOperations() public {
_stakingOperations();
}

function getBondingRequest(uint256 _id) public view returns (BondingRequest memory) {
return bondingMap[_id];
}
Expand Down
5 changes: 3 additions & 2 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import (
"encoding/binary"
"errors"
"fmt"
"math/big"
"sync"

"github.com/autonity/autonity/common"
"github.com/autonity/autonity/common/math"
"github.com/autonity/autonity/crypto"
Expand All @@ -29,8 +32,6 @@ import (
"github.com/autonity/autonity/crypto/bn256"
"github.com/autonity/autonity/p2p/enode"
"github.com/autonity/autonity/params"
"math/big"
"sync"

// lint:ignore SA1019 Needed for precompile
"golang.org/x/crypto/ripemd160"
Expand Down
1,025 changes: 1,025 additions & 0 deletions params/generated/AccountabilityTest.go

Large diffs are not rendered by default.

1,957 changes: 1,957 additions & 0 deletions params/generated/AutonityTest.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion params/protocol_contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ type AutonityContractGenesis struct {
Treasury common.Address `json:"treasury"`
TreasuryFee uint64 `json:"treasuryFee"`
DelegationRate uint64 `json:"delegationRate"`
Validators []*Validator `json:"validators"`
Validators []*Validator `json:"validators"` // todo: Can we change that to []Validator
}

type AccountabilityGenesis struct {
Expand Down

0 comments on commit ee8c11f

Please sign in to comment.