Skip to content

Commit

Permalink
precompiled: adjust the gas cost of consortium precompiled contract (a…
Browse files Browse the repository at this point in the history
…xieinfinity#376)

* precompiled: move contract ABI unmarshal to init

Currently, every time the precompiled contract is called, the contract ABI is
unmarshalled even though the ABI is unchanged. This commit moves the contract ABI
unmarshal to init to save the computation. This commit also optimizes the
SortableValidators.Less to reduce the memory allocation.

* precompiled: adjust the gas cost of consortium precompiled contract

This commit adds the benchmarks and calculated gas cost from the benchmarks to
validator sorting, pick validator set and verify double sign proof precompiled
contracts. Ethereum aims for 30 mgas/s when setting gas cost for precompiled
contracts, we aims for 60 mgas/s.

Benchmark results:

BenchmarkConsortiumValidatorSorting/validator-sort-Gas=2310-8             548737             20101 ns/op              2310 gas/op              114.9 mgas/s        11616 B/op        224 allocs/op
BenchmarkConsortiumValidatorSorting/validator-sort-Gas=9780-8             205410             66083 ns/op              9780 gas/op              148.0 mgas/s        49280 B/op        852 allocs/op
BenchmarkConsortiumVerifyHeaders/verify-headers-Gas=21000-8                48445            232612 ns/op             21000 gas/op               90.27 mgas/s       53043 B/op        477 allocs/op
BenchmarkConsortiumPickValidatorSet/pick-validator-set-Gas=3540-8         377305             32748 ns/op              3540 gas/op              108.1 mgas/s        19775 B/op        299 allocs/op
BenchmarkConsortiumPickValidatorSet/pick-validator-set-Gas=14790-8        113479            129076 ns/op             14790 gas/op              114.6 mgas/s        76689 B/op       1092 allocs/op
  • Loading branch information
minh-bq committed Dec 29, 2023
1 parent de339cd commit 7760cd0
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 94 deletions.
166 changes: 115 additions & 51 deletions core/vm/consortium_precompiled_contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,34 @@ import (
"golang.org/x/crypto/sha3"
)

const (
LogContract = iota
SortValidator
VerifyHeaders
PickValidatorSet
GetDoubleSignSlashingConfig
ValidateFinalityVoteProof
NumOfAbis
)

var (
consortiumLogAbi = `[{"inputs":[{"internalType":"string","name":"message","type":"string"}],"name":"log","outputs":[],"stateMutability":"nonpayable","type":"function"}]`
consortiumSortValidatorAbi = `[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"weights","type":"uint256[]"}],"name":"sortValidators","outputs":[{"internalType":"address[]","name":"_validators","type":"address[]"}],"stateMutability":"view","type":"function"}]`
consortiumVerifyHeadersAbi = `[{"outputs":[],"name":"getHeader","inputs":[{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"bytes32","name":"parentHash","type":"bytes32"},{"internalType":"bytes32","name":"ommersHash","type":"bytes32"},{"internalType":"address","name":"coinbase","type":"address"},{"internalType":"bytes32","name":"stateRoot","type":"bytes32"},{"internalType":"bytes32","name":"transactionsRoot","type":"bytes32"},{"internalType":"bytes32","name":"receiptsRoot","type":"bytes32"},{"internalType":"uint8[256]","name":"logsBloom","type":"uint8[256]"},{"internalType":"uint256","name":"difficulty","type":"uint256"},{"internalType":"uint256","name":"number","type":"uint256"},{"internalType":"uint64","name":"gasLimit","type":"uint64"},{"internalType":"uint64","name":"gasUsed","type":"uint64"},{"internalType":"uint64","name":"timestamp","type":"uint64"},{"internalType":"bytes","name":"extraData","type":"bytes"},{"internalType":"bytes32","name":"mixHash","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"consensusAddr","type":"address"},{"internalType":"bytes","name":"header1","type":"bytes"},{"internalType":"bytes","name":"header2","type":"bytes"}],"name":"validatingDoubleSignProof","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]`
consortiumPickValidatorSetAbi = `[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address[]","name":"_candidates","type":"address[]"},{"internalType":"uint256[]","name":"_weights","type":"uint256[]"},{"internalType":"uint256[]","name":"_trustedWeights","type":"uint256[]"},{"internalType":"uint256","name":"_maxValidatorNumber","type":"uint256"},{"internalType":"uint256","name":"_maxPrioritizedValidatorNumber","type":"uint256"}],"name":"pickValidatorSet","outputs":[{"internalType":"address[]","name":"_validators","type":"address[]"}],"stateMutability":"view","type":"function"}]`
getDoubleSignSlashingConfigsAbi = `[{"inputs":[],"name":"getDoubleSignSlashingConfigs","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]`
validateFinalityVoteProofAbi = `[{"inputs":[{"internalType":"bytes","name":"voterPublicKey","type":"bytes"},{"internalType":"uint256","name":"targetBlockNumber","type":"uint256"},{"internalType":"bytes32[2]","name":"targetBlockHash","type":"bytes32[2]"},{"internalType":"bytes[][2]","name":"listOfPublicKey","type":"bytes[][2]"},{"internalType":"bytes[2]","name":"aggregatedSignature","type":"bytes[2]"}],"name":"validateFinalityVoteProof","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]`
rawConsortiumLogAbi = `[{"inputs":[{"internalType":"string","name":"message","type":"string"}],"name":"log","outputs":[],"stateMutability":"nonpayable","type":"function"}]`
rawConsortiumSortValidatorAbi = `[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address[]","name":"validators","type":"address[]"},{"internalType":"uint256[]","name":"weights","type":"uint256[]"}],"name":"sortValidators","outputs":[{"internalType":"address[]","name":"_validators","type":"address[]"}],"stateMutability":"view","type":"function"}]`
rawConsortiumVerifyHeadersAbi = `[{"outputs":[],"name":"getHeader","inputs":[{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"bytes32","name":"parentHash","type":"bytes32"},{"internalType":"bytes32","name":"ommersHash","type":"bytes32"},{"internalType":"address","name":"coinbase","type":"address"},{"internalType":"bytes32","name":"stateRoot","type":"bytes32"},{"internalType":"bytes32","name":"transactionsRoot","type":"bytes32"},{"internalType":"bytes32","name":"receiptsRoot","type":"bytes32"},{"internalType":"uint8[256]","name":"logsBloom","type":"uint8[256]"},{"internalType":"uint256","name":"difficulty","type":"uint256"},{"internalType":"uint256","name":"number","type":"uint256"},{"internalType":"uint64","name":"gasLimit","type":"uint64"},{"internalType":"uint64","name":"gasUsed","type":"uint64"},{"internalType":"uint64","name":"timestamp","type":"uint64"},{"internalType":"bytes","name":"extraData","type":"bytes"},{"internalType":"bytes32","name":"mixHash","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"consensusAddr","type":"address"},{"internalType":"bytes","name":"header1","type":"bytes"},{"internalType":"bytes","name":"header2","type":"bytes"}],"name":"validatingDoubleSignProof","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]`
rawConsortiumPickValidatorSetAbi = `[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address[]","name":"_candidates","type":"address[]"},{"internalType":"uint256[]","name":"_weights","type":"uint256[]"},{"internalType":"uint256[]","name":"_trustedWeights","type":"uint256[]"},{"internalType":"uint256","name":"_maxValidatorNumber","type":"uint256"},{"internalType":"uint256","name":"_maxPrioritizedValidatorNumber","type":"uint256"}],"name":"pickValidatorSet","outputs":[{"internalType":"address[]","name":"_validators","type":"address[]"}],"stateMutability":"view","type":"function"}]`
rawGetDoubleSignSlashingConfigsAbi = `[{"inputs":[],"name":"getDoubleSignSlashingConfigs","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]`
rawValidateFinalityVoteProofAbi = `[{"inputs":[{"internalType":"bytes","name":"voterPublicKey","type":"bytes"},{"internalType":"uint256","name":"targetBlockNumber","type":"uint256"},{"internalType":"bytes32[2]","name":"targetBlockHash","type":"bytes32[2]"},{"internalType":"bytes[][2]","name":"listOfPublicKey","type":"bytes[][2]"},{"internalType":"bytes[2]","name":"aggregatedSignature","type":"bytes[2]"}],"name":"validateFinalityVoteProof","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]`

rawABIs = [NumOfAbis]string{
LogContract: rawConsortiumLogAbi,
SortValidator: rawConsortiumSortValidatorAbi,
VerifyHeaders: rawConsortiumVerifyHeadersAbi,
PickValidatorSet: rawConsortiumPickValidatorSetAbi,
GetDoubleSignSlashingConfig: rawGetDoubleSignSlashingConfigsAbi,
ValidateFinalityVoteProof: rawValidateFinalityVoteProofAbi,
}

unmarshalledABIs = [NumOfAbis]*abi.ABI{}
)

const (
Expand All @@ -48,6 +69,17 @@ const (
maxBlsPublicKeyListLength = 100
)

func init() {
for i, rawABI := range rawABIs {
unmarshalledABI, err := abi.JSON(strings.NewReader(rawABI))
if err != nil {
log.Error("Failed to unmarshalled precompiled ABI", "num", i)
} else {
unmarshalledABIs[i] = &unmarshalledABI
}
}
}

func PrecompiledContractsConsortium(caller ContractRef, evm *EVM) map[common.Address]PrecompiledContract {
return map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{101}): &consortiumLog{},
Expand All @@ -68,7 +100,7 @@ func (c *consortiumLog) Run(input []byte) ([]byte, error) {
if os.Getenv("DEBUG") != "true" {
return input, nil
}
_, method, args, err := loadMethodAndArgs(consortiumLogAbi, input)
_, method, args, err := loadMethodAndArgs(LogContract, input)
if err != nil {
return nil, err
}
Expand All @@ -84,24 +116,44 @@ func (c *consortiumLog) Run(input []byte) ([]byte, error) {
return input, nil
}

func isSystemContractCaller(caller ContractRef, evm *EVM) error {
// These 2 fields are nil in benchmark only
if caller != nil && evm != nil {
if evm.ChainConfig().ConsortiumV2Contracts == nil {
return errors.New("cannot find consortium v2 contracts")
}
if !evm.ChainConfig().ConsortiumV2Contracts.IsSystemContract(caller.Address()) {
return errors.New("unauthorized sender")
}
}

return nil
}

type consortiumPickValidatorSet struct {
caller ContractRef
evm *EVM
}

func (c *consortiumPickValidatorSet) RequiredGas(_ []byte) uint64 {
return 0
func (c *consortiumPickValidatorSet) RequiredGas(input []byte) uint64 {
// c.evm is nil in benchmark
if c.evm == nil || c.evm.chainRules.IsMiko {
// We approximate the number of validators by dividing the length of input by
// length of address (20). This is likely an overestimation because there are
// slices of weight, maxValidatorNumber and maxPrioritizedValidatorNumber in
// the input too.
return uint64((len(input) / common.AddressLength)) * params.ValidatorSortingBaseGas
} else {
return 0
}
}

func (c *consortiumPickValidatorSet) Run(input []byte) ([]byte, error) {
if c.evm.ChainConfig().ConsortiumV2Contracts == nil {
return nil, errors.New("cannot find consortium v2 contracts")
}
if !c.evm.ChainConfig().ConsortiumV2Contracts.IsSystemContract(c.caller.Address()) {
return nil, errors.New("unauthorized sender")
if err := isSystemContractCaller(c.caller, c.evm); err != nil {
return nil, err
}
// get method, args from abi
_, method, args, err := loadMethodAndArgs(consortiumPickValidatorSetAbi, input)
_, method, args, err := loadMethodAndArgs(PickValidatorSet, input)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -216,19 +268,24 @@ type consortiumValidatorSorting struct {
evm *EVM
}

func (c *consortiumValidatorSorting) RequiredGas(_ []byte) uint64 {
return 0
func (c *consortiumValidatorSorting) RequiredGas(input []byte) uint64 {
// c.evm is nil in benchmark
if c.evm == nil || c.evm.chainRules.IsMiko {
// We approximate the number of validators by dividing the length of input by
// length of address (20). This is likely an overestimation because there is
// a slice of weight in the input too.
return uint64((len(input) / common.AddressLength)) * params.ValidatorSortingBaseGas
} else {
return 0
}
}

func (c *consortiumValidatorSorting) Run(input []byte) ([]byte, error) {
if c.evm.ChainConfig().ConsortiumV2Contracts == nil {
return nil, errors.New("cannot find consortium v2 contracts")
}
if !c.evm.ChainConfig().ConsortiumV2Contracts.IsSystemContract(c.caller.Address()) {
return nil, errors.New("unauthorized sender")
if err := isSystemContractCaller(c.caller, c.evm); err != nil {
return nil, err
}
// get method, args from abi
_, method, args, err := loadMethodAndArgs(consortiumSortValidatorAbi, input)
_, method, args, err := loadMethodAndArgs(SortValidator, input)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -271,8 +328,12 @@ func (s *SortableValidators) Len() int {

func (s *SortableValidators) Less(i, j int) bool {
cmp := s.weights[i].Cmp(s.weights[j])
addrsCmp := big.NewInt(0).SetBytes(s.validators[i].Bytes()).Cmp(big.NewInt(0).SetBytes(s.validators[j].Bytes())) > 0
return cmp > 0 || (cmp == 0 && addrsCmp)

if cmp == 0 {
return new(big.Int).SetBytes(s.validators[i].Bytes()).Cmp(new(big.Int).SetBytes(s.validators[j].Bytes())) > 0
}

return cmp > 0
}

func (s *SortableValidators) Swap(i, j int) {
Expand Down Expand Up @@ -335,16 +396,17 @@ func staticCall(evm *EVM, smcAbi abi.ABI, method string, contract, sender common
return out, nil
}

func loadMethodAndArgs(smcAbi string, input []byte) (abi.ABI, *abi.Method, []interface{}, error) {
func loadMethodAndArgs(contractIndex int, input []byte) (abi.ABI, *abi.Method, []interface{}, error) {
var (
pAbi abi.ABI
err error
method *abi.Method
args []interface{}
)
if pAbi, err = abi.JSON(strings.NewReader(smcAbi)); err != nil {
return abi.ABI{}, nil, nil, err
if contractIndex < 0 || contractIndex >= len(unmarshalledABIs) || unmarshalledABIs[contractIndex] == nil {
return abi.ABI{}, nil, nil, errors.New("invalid contract index")
}
pAbi = *unmarshalledABIs[contractIndex]
if method, err = pAbi.MethodById(input); err != nil {
return abi.ABI{}, nil, nil, err
}
Expand All @@ -364,18 +426,20 @@ type consortiumVerifyHeaders struct {
}

func (c *consortiumVerifyHeaders) RequiredGas(_ []byte) uint64 {
return 0
// c.evm is nil in benchmark
if c.evm == nil || c.evm.chainRules.IsMiko {
return params.VerifyFinalityHeadersProofGas
} else {
return 0
}
}

func (c *consortiumVerifyHeaders) Run(input []byte) ([]byte, error) {
if c.evm.ChainConfig().ConsortiumV2Contracts == nil {
return nil, errors.New("cannot find consortium v2 contracts")
}
if !c.evm.ChainConfig().ConsortiumV2Contracts.IsSystemContract(c.caller.Address()) {
return nil, errors.New("unauthorized sender")
if err := isSystemContractCaller(c.caller, c.evm); err != nil {
return nil, err
}
// get method, args from abi
smcAbi, method, args, err := loadMethodAndArgs(consortiumVerifyHeadersAbi, input)
smcAbi, method, args, err := loadMethodAndArgs(VerifyHeaders, input)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -438,7 +502,8 @@ func (c *consortiumVerifyHeaders) getSigner(header types.BlockHeader) (common.Ad
func (c *consortiumVerifyHeaders) verify(consensusAddr common.Address, header1, header2 types.BlockHeader) bool {
var maxOffset *big.Int

if !c.evm.chainConfig.IsConsortiumV2(header1.Number) {
// c.evm s nil in benchmark, so we skip this check in benchmark
if c.evm != nil && !c.evm.chainConfig.IsConsortiumV2(header1.Number) {
return false
}
if header1.ToHeader().ParentHash.Hex() != header2.ToHeader().ParentHash.Hex() {
Expand All @@ -460,7 +525,10 @@ func (c *consortiumVerifyHeaders) verify(consensusAddr common.Address, header1,
log.Trace("[consortiumVerifyHeaders][verify] error while getting signer from header2", "err", err)
return false
}
methodAbi, _ := abi.JSON(strings.NewReader(getDoubleSignSlashingConfigsAbi))
if unmarshalledABIs[GetDoubleSignSlashingConfig] == nil {
return false
}
methodAbi := *unmarshalledABIs[GetDoubleSignSlashingConfig]

if c.test {
maxOffset = big.NewInt(doubleSigningOffsetTest)
Expand All @@ -487,10 +555,13 @@ func (c *consortiumVerifyHeaders) verify(consensusAddr common.Address, header1,
}
}

currentBlock := c.evm.Context.BlockNumber
// What if current block < header1.Number?
if currentBlock.Cmp(header1.Number) > 0 && new(big.Int).Sub(currentBlock, header1.Number).Cmp(maxOffset) > 0 {
return false
// c.evm is nil in benchmark, so we skip this check in benchmark
if c.evm != nil {
currentBlock := c.evm.Context.BlockNumber
// What if current block < header1.Number?
if currentBlock.Cmp(header1.Number) > 0 && new(big.Int).Sub(currentBlock, header1.Number).Cmp(maxOffset) > 0 {
return false
}
}

return signer1.Hex() == signer2.Hex() &&
Expand Down Expand Up @@ -540,17 +611,10 @@ func (contract *consortiumValidateFinalityProof) RequiredGas(input []byte) uint6
}

func (contract *consortiumValidateFinalityProof) Run(input []byte) ([]byte, error) {
// These 2 fields are nil in testing only
if contract.caller != nil && contract.evm != nil {
if contract.evm.ChainConfig().ConsortiumV2Contracts == nil {
return nil, errors.New("cannot find consortium v2 contracts")
}
if !contract.evm.ChainConfig().ConsortiumV2Contracts.IsSystemContract(contract.caller.Address()) {
return nil, errors.New("unauthorized sender")
}
if err := isSystemContractCaller(contract.caller, contract.evm); err != nil {
return nil, err
}

_, method, args, err := loadMethodAndArgs(validateFinalityVoteProofAbi, input)
_, method, args, err := loadMethodAndArgs(ValidateFinalityVoteProof, input)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 7760cd0

Please sign in to comment.