mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-11 06:23:47 +00:00
commit
769e99b1eb
5 changed files with 40 additions and 109 deletions
|
|
@ -32,7 +32,7 @@ import (
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const validatorsetABI = "[{\"constant\":false,\"inputs\":[],\"name\":\"finalizeChange\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"},{\"name\":\"\",\"type\":\"uint256[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"validator\",\"type\":\"address\"},{\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"name\":\"proof\",\"type\":\"bytes\"}],\"name\":\"reportMalicious\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"validator\",\"type\":\"address\"},{\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"reportBenign\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_parentHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"_newSet\",\"type\":\"address[]\"}],\"name\":\"InitiateChange\",\"type\":\"event\"}]"
|
const validatorsetABI = `[{"constant":true,"inputs":[],"name":"getInitialValidators","outputs":[{"name":"","type":"address[]"},{"name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"},{"name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"}]`
|
||||||
|
|
||||||
const (
|
const (
|
||||||
voteSnapshotInterval = 1024 // Number of blocks after which to save the vote snapshot to the database
|
voteSnapshotInterval = 1024 // Number of blocks after which to save the vote snapshot to the database
|
||||||
|
|
@ -44,7 +44,7 @@ const (
|
||||||
|
|
||||||
// Bor protocol constants.
|
// Bor protocol constants.
|
||||||
var (
|
var (
|
||||||
epochLength = uint64(30000) // Default number of blocks after which to checkpoint and reset the pending votes
|
defaultSprintLength = uint64(64) // Default number of blocks after which to checkpoint and reset the pending votes
|
||||||
|
|
||||||
extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity
|
extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity
|
||||||
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
|
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
|
||||||
|
|
@ -251,8 +251,8 @@ type Bor struct {
|
||||||
func New(config *params.BorConfig, db ethdb.Database, ethAPI *ethapi.PublicBlockChainAPI) *Bor {
|
func New(config *params.BorConfig, db ethdb.Database, ethAPI *ethapi.PublicBlockChainAPI) *Bor {
|
||||||
// Set any missing consensus parameters to their defaults
|
// Set any missing consensus parameters to their defaults
|
||||||
conf := *config
|
conf := *config
|
||||||
if conf.Epoch == 0 {
|
if conf.Sprint == 0 {
|
||||||
conf.Epoch = epochLength
|
conf.Sprint = defaultSprintLength
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate the snapshot caches and create the engine
|
// Allocate the snapshot caches and create the engine
|
||||||
|
|
@ -318,7 +318,7 @@ func (c *Bor) verifyHeader(chain consensus.ChainReader, header *types.Header, pa
|
||||||
return consensus.ErrFutureBlock
|
return consensus.ErrFutureBlock
|
||||||
}
|
}
|
||||||
// Checkpoint blocks need to enforce zero beneficiary
|
// Checkpoint blocks need to enforce zero beneficiary
|
||||||
checkpoint := (number % c.config.Epoch) == 0
|
checkpoint := (number % c.config.Sprint) == 0
|
||||||
if checkpoint && header.Coinbase != (common.Address{}) {
|
if checkpoint && header.Coinbase != (common.Address{}) {
|
||||||
return errInvalidCheckpointBeneficiary
|
return errInvalidCheckpointBeneficiary
|
||||||
}
|
}
|
||||||
|
|
@ -397,7 +397,7 @@ func (c *Bor) verifyCascadingFields(chain consensus.ChainReader, header *types.H
|
||||||
|
|
||||||
// If the block is a checkpoint block, verify the signer list
|
// If the block is a checkpoint block, verify the signer list
|
||||||
// TODO verify signers
|
// TODO verify signers
|
||||||
if number%c.config.Epoch == 0 {
|
if number%c.config.Sprint == 0 {
|
||||||
// signers := make([]byte, len(snap.Signers)*common.AddressLength)
|
// signers := make([]byte, len(snap.Signers)*common.AddressLength)
|
||||||
// for i, signer := range snap.signers() {
|
// for i, signer := range snap.signers() {
|
||||||
// copy(signers[i*common.AddressLength:], signer[:])
|
// copy(signers[i*common.AddressLength:], signer[:])
|
||||||
|
|
@ -434,7 +434,7 @@ func (c *Bor) snapshot(chain consensus.ChainReader, number uint64, hash common.H
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're at an checkpoint block, make a snapshot if it's known
|
// If we're at an checkpoint block, make a snapshot if it's known
|
||||||
if number == 0 || (number%c.config.Epoch == 0 && chain.GetHeaderByNumber(number-1) == nil) {
|
if number == 0 || (number%c.config.Sprint == 0 && chain.GetHeaderByNumber(number-1) == nil) {
|
||||||
checkpoint := chain.GetHeaderByNumber(number)
|
checkpoint := chain.GetHeaderByNumber(number)
|
||||||
if checkpoint != nil {
|
if checkpoint != nil {
|
||||||
hash := checkpoint.Hash()
|
hash := checkpoint.Hash()
|
||||||
|
|
@ -543,7 +543,7 @@ func (c *Bor) verifySeal(chain consensus.ChainReader, header *types.Header, pare
|
||||||
validators := snap.ValidatorSet.Validators
|
validators := snap.ValidatorSet.Validators
|
||||||
// proposer will be the last signer if block is not epoch block
|
// proposer will be the last signer if block is not epoch block
|
||||||
proposer := snap.ValidatorSet.GetProposer().Address
|
proposer := snap.ValidatorSet.GetProposer().Address
|
||||||
if number%c.config.Epoch != 0 {
|
if number%c.config.Sprint != 0 {
|
||||||
// proposer = snap.Recents[number-1]
|
// proposer = snap.Recents[number-1]
|
||||||
}
|
}
|
||||||
proposerIndex, _ := snap.ValidatorSet.GetByAddress(proposer)
|
proposerIndex, _ := snap.ValidatorSet.GetByAddress(proposer)
|
||||||
|
|
@ -564,7 +564,7 @@ func (c *Bor) verifySeal(chain consensus.ChainReader, header *types.Header, pare
|
||||||
|
|
||||||
// Ensure that the difficulty corresponds to the turn-ness of the signer
|
// Ensure that the difficulty corresponds to the turn-ness of the signer
|
||||||
if !c.fakeDiff {
|
if !c.fakeDiff {
|
||||||
difficulty := snap.inturn(header.Number.Uint64(), signer, c.config.Epoch)
|
difficulty := snap.inturn(header.Number.Uint64(), signer, c.config.Sprint)
|
||||||
if header.Difficulty.Uint64() != difficulty {
|
if header.Difficulty.Uint64() != difficulty {
|
||||||
return errWrongDifficulty
|
return errWrongDifficulty
|
||||||
}
|
}
|
||||||
|
|
@ -589,7 +589,7 @@ func (c *Bor) Prepare(chain consensus.ChainReader, header *types.Header) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the correct difficulty
|
// Set the correct difficulty
|
||||||
header.Difficulty = CalcDifficulty(snap, c.signer, c.config.Epoch)
|
header.Difficulty = CalcDifficulty(snap, c.signer, c.config.Sprint)
|
||||||
|
|
||||||
// Ensure the extra data has all it's components
|
// Ensure the extra data has all it's components
|
||||||
if len(header.Extra) < extraVanity {
|
if len(header.Extra) < extraVanity {
|
||||||
|
|
@ -597,7 +597,7 @@ func (c *Bor) Prepare(chain consensus.ChainReader, header *types.Header) error {
|
||||||
}
|
}
|
||||||
header.Extra = header.Extra[:extraVanity]
|
header.Extra = header.Extra[:extraVanity]
|
||||||
|
|
||||||
if number%c.config.Epoch == 0 {
|
if number%c.config.Sprint == 0 {
|
||||||
for _, signer := range snap.signers() {
|
for _, signer := range snap.signers() {
|
||||||
header.Extra = append(header.Extra, signer[:]...)
|
header.Extra = append(header.Extra, signer[:]...)
|
||||||
}
|
}
|
||||||
|
|
@ -613,7 +613,7 @@ func (c *Bor) Prepare(chain consensus.ChainReader, header *types.Header) error {
|
||||||
return consensus.ErrUnknownAncestor
|
return consensus.ErrUnknownAncestor
|
||||||
}
|
}
|
||||||
|
|
||||||
header.Time = parent.Time + CalcProducerDelay(snap, c.signer, c.config.Period, c.config.Epoch, c.config.ProducerDelay)
|
header.Time = parent.Time + CalcProducerDelay(snap, c.signer, c.config.Period, c.config.Sprint, c.config.ProducerDelay)
|
||||||
if header.Time < uint64(time.Now().Unix()) {
|
if header.Time < uint64(time.Now().Unix()) {
|
||||||
header.Time = uint64(time.Now().Unix())
|
header.Time = uint64(time.Now().Unix())
|
||||||
}
|
}
|
||||||
|
|
@ -683,7 +683,7 @@ func (c *Bor) Seal(chain consensus.ChainReader, block *types.Block, results chan
|
||||||
validators := snap.ValidatorSet.Validators
|
validators := snap.ValidatorSet.Validators
|
||||||
// proposer will be the last signer if block is not epoch block
|
// proposer will be the last signer if block is not epoch block
|
||||||
proposer := snap.ValidatorSet.GetProposer().Address
|
proposer := snap.ValidatorSet.GetProposer().Address
|
||||||
if number%c.config.Epoch != 0 {
|
if number%c.config.Sprint != 0 {
|
||||||
// proposer = snap.Recents[number-1]
|
// proposer = snap.Recents[number-1]
|
||||||
}
|
}
|
||||||
proposerIndex, _ := snap.ValidatorSet.GetByAddress(proposer)
|
proposerIndex, _ := snap.ValidatorSet.GetByAddress(proposer)
|
||||||
|
|
@ -743,7 +743,7 @@ func (c *Bor) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *t
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return CalcDifficulty(snap, c.signer, c.config.Epoch)
|
return CalcDifficulty(snap, c.signer, c.config.Sprint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SealHash returns the hash of a block prior to it being sealed.
|
// SealHash returns the hash of a block prior to it being sealed.
|
||||||
|
|
@ -769,16 +769,22 @@ func (c *Bor) Close() error {
|
||||||
|
|
||||||
// GetCurrentValidators get current validators
|
// GetCurrentValidators get current validators
|
||||||
func (c *Bor) GetCurrentValidators(number uint64) ([]*Validator, error) {
|
func (c *Bor) GetCurrentValidators(number uint64) ([]*Validator, error) {
|
||||||
return GetValidators(number, c.config.ValidatorContract, c.ethAPI)
|
return GetValidators(number, c.config.Sprint, c.config.ValidatorContract, c.ethAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValidators get current validators
|
// GetValidators get current validators
|
||||||
func GetValidators(number uint64, validatorContract string, ethAPI *ethapi.PublicBlockChainAPI) ([]*Validator, error) {
|
func GetValidators(number uint64, sprint uint64, validatorContract string, ethAPI *ethapi.PublicBlockChainAPI) ([]*Validator, error) {
|
||||||
blockNr := rpc.BlockNumber(number)
|
blockNr := rpc.BlockNumber(number)
|
||||||
|
|
||||||
|
// method
|
||||||
|
method := "getValidators"
|
||||||
|
if number < sprint {
|
||||||
|
method = "getInitialValidators"
|
||||||
|
}
|
||||||
|
|
||||||
// validator set ABI
|
// validator set ABI
|
||||||
validatorSetABI, _ := abi.JSON(strings.NewReader(validatorsetABI))
|
validatorSetABI, _ := abi.JSON(strings.NewReader(validatorsetABI))
|
||||||
data, err := validatorSetABI.Pack("getValidators")
|
data, err := validatorSetABI.Pack(method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Unable to pack tx for getValidator", "error", err)
|
fmt.Println("Unable to pack tx for getValidator", "error", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -810,7 +816,7 @@ func GetValidators(number uint64, validatorContract string, ethAPI *ethapi.Publi
|
||||||
ret1,
|
ret1,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validatorSetABI.Unpack(out, "getValidators", result); err != nil {
|
if err := validatorSetABI.Unpack(out, method, result); err != nil {
|
||||||
fmt.Println("err", err)
|
fmt.Println("err", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -822,6 +828,9 @@ func GetValidators(number uint64, validatorContract string, ethAPI *ethapi.Publi
|
||||||
VotingPower: (*ret1)[i].Int64(),
|
VotingPower: (*ret1)[i].Int64(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println(method)
|
||||||
|
fmt.Println(" === ", valz)
|
||||||
return valz, nil
|
return valz, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -198,14 +198,14 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
|
||||||
for _, header := range headers {
|
for _, header := range headers {
|
||||||
// Remove any votes on checkpoint blocks
|
// Remove any votes on checkpoint blocks
|
||||||
number := header.Number.Uint64()
|
number := header.Number.Uint64()
|
||||||
if (number+1)%s.config.Epoch == 0 {
|
if (number+1)%s.config.Sprint == 0 {
|
||||||
// snap.Votes = nil
|
// snap.Votes = nil
|
||||||
// snap.Tally = make(map[common.Address]Tally)
|
// snap.Tally = make(map[common.Address]Tally)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the oldest signer from the recent list to allow it signing again
|
// Delete the oldest signer from the recent list to allow it signing again
|
||||||
if number >= s.config.Epoch && number-s.config.Epoch >= 0 {
|
if number >= s.config.Sprint && number-s.config.Sprint >= 0 {
|
||||||
delete(snap.Recents, number-s.config.Epoch)
|
delete(snap.Recents, number-s.config.Sprint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the authorization key and check against signers
|
// Resolve the authorization key and check against signers
|
||||||
|
|
@ -225,7 +225,7 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
|
||||||
validators := snap.ValidatorSet.Validators
|
validators := snap.ValidatorSet.Validators
|
||||||
// proposer will be the last signer if block is not epoch block
|
// proposer will be the last signer if block is not epoch block
|
||||||
proposer := snap.ValidatorSet.GetProposer().Address
|
proposer := snap.ValidatorSet.GetProposer().Address
|
||||||
// if number%s.config.Epoch != 0 {
|
// if number%s.config.Sprint != 0 {
|
||||||
// proposer = snap.Recents[number-1]
|
// proposer = snap.Recents[number-1]
|
||||||
// }
|
// }
|
||||||
proposerIndex, _ := snap.ValidatorSet.GetByAddress(proposer)
|
proposerIndex, _ := snap.ValidatorSet.GetByAddress(proposer)
|
||||||
|
|
@ -246,10 +246,11 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
|
||||||
|
|
||||||
// add recents
|
// add recents
|
||||||
snap.Recents[number] = signer
|
snap.Recents[number] = signer
|
||||||
|
// TODO remove
|
||||||
fmt.Println("Recent signer", "number", number, "signer", signer.Hex())
|
fmt.Println("Recent signer", "number", number, "signer", signer.Hex())
|
||||||
// change proposer on epoch
|
// change proposer on epoch
|
||||||
if number > 0 && (number+1)%s.config.Epoch == 0 {
|
if number > 0 && (number+1)%s.config.Sprint == 0 {
|
||||||
newVals, _ := GetValidators(number, snap.config.ValidatorContract, snap.ethAPI)
|
newVals, _ := GetValidators(number, s.config.Sprint, s.config.ValidatorContract, snap.ethAPI)
|
||||||
v := getUpdatedValidatorSet(snap.ValidatorSet.Copy(), newVals)
|
v := getUpdatedValidatorSet(snap.ValidatorSet.Copy(), newVals)
|
||||||
v.IncrementProposerPriority(1)
|
v.IncrementProposerPriority(1)
|
||||||
snap.ValidatorSet = v
|
snap.ValidatorSet = v
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
pragma solidity 0.5.9;
|
|
||||||
|
|
||||||
import { ValidatorSet } from "./ValidatorSet.sol";
|
|
||||||
|
|
||||||
contract BorValidatorSet is ValidatorSet {
|
|
||||||
/// Issue this log event to signal a desired change in validator set.
|
|
||||||
/// This will not lead to a change in active validator set until
|
|
||||||
/// finalizeChange is called.
|
|
||||||
///
|
|
||||||
/// Only the last log event of any block can take effect.
|
|
||||||
/// If a signal is issued while another is being finalized it may never
|
|
||||||
/// take effect.
|
|
||||||
///
|
|
||||||
/// _parentHash here should be the parent block hash, or the
|
|
||||||
/// signal will not be recognized.
|
|
||||||
event InitiateChange(bytes32 indexed _parentHash, address[] _newSet);
|
|
||||||
|
|
||||||
/// Called when an initiated change reaches finality and is activated.
|
|
||||||
/// Only valid when msg.sender == SYSTEM (EIP96, 2**160 - 2).
|
|
||||||
///
|
|
||||||
/// Also called when the contract is first enabled for consensus. In this case,
|
|
||||||
/// the "change" finalized is the activation of the initial set.
|
|
||||||
function finalizeChange() external {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reports benign misbehavior of validator of the current validator set
|
|
||||||
/// (e.g. validator offline).
|
|
||||||
function reportBenign(address validator, uint256 blockNumber) external {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reports malicious misbehavior of validator of the current validator set
|
|
||||||
/// and provides proof of that misbehavor, which varies by engine
|
|
||||||
/// (e.g. double vote).
|
|
||||||
function reportMalicious(address validator, uint256 blockNumber, bytes calldata proof) external {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get current validator set (last enacted or initial if no changes ever made) with current stake.
|
|
||||||
function getValidators() external view returns (address[] memory, uint256[] memory) {
|
|
||||||
address[] memory d = new address[](4);
|
|
||||||
d[0] = 0x9fB29AAc15b9A4B7F17c3385939b007540f4d791;
|
|
||||||
d[1] = 0x96C42C56fdb78294F96B0cFa33c92bed7D75F96a;
|
|
||||||
d[2] = 0x7D58F677794ECdB751332c9A507993dB1b008874;
|
|
||||||
d[3] = 0xE4F1A86989758D4aC65671855B9a29B843bb865D;
|
|
||||||
uint256[] memory p = new uint256[](4);
|
|
||||||
p[0] = 10;
|
|
||||||
p[1] = 20;
|
|
||||||
p[2] = 30;
|
|
||||||
p[3] = 40;
|
|
||||||
return (d, p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +1,11 @@
|
||||||
pragma solidity 0.5.9;
|
pragma solidity 0.5.9;
|
||||||
|
|
||||||
interface ValidatorSet {
|
interface ValidatorSet {
|
||||||
/// Issue this log event to signal a desired change in validator set.
|
/// Get initial validator set
|
||||||
/// This will not lead to a change in active validator set until
|
function getInitialValidators()
|
||||||
/// finalizeChange is called.
|
external
|
||||||
///
|
view
|
||||||
/// Only the last log event of any block can take effect.
|
returns (address[] memory, uint256[] memory);
|
||||||
/// If a signal is issued while another is being finalized it may never
|
|
||||||
/// take effect.
|
|
||||||
///
|
|
||||||
/// _parentHash here should be the parent block hash, or the
|
|
||||||
/// signal will not be recognized.
|
|
||||||
event InitiateChange(bytes32 indexed _parentHash, address[] _newSet);
|
|
||||||
|
|
||||||
/// Called when an initiated change reaches finality and is activated.
|
|
||||||
/// Only valid when msg.sender == SYSTEM (EIP96, 2**160 - 2).
|
|
||||||
///
|
|
||||||
/// Also called when the contract is first enabled for consensus. In this case,
|
|
||||||
/// the "change" finalized is the activation of the initial set.
|
|
||||||
function finalizeChange()
|
|
||||||
external;
|
|
||||||
|
|
||||||
/// Reports benign misbehavior of validator of the current validator set
|
|
||||||
/// (e.g. validator offline).
|
|
||||||
function reportBenign(address validator, uint256 blockNumber)
|
|
||||||
external;
|
|
||||||
|
|
||||||
/// Reports malicious misbehavior of validator of the current validator set
|
|
||||||
/// and provides proof of that misbehavor, which varies by engine
|
|
||||||
/// (e.g. double vote).
|
|
||||||
function reportMalicious(address validator, uint256 blockNumber, bytes calldata proof)
|
|
||||||
external;
|
|
||||||
|
|
||||||
/// Get current validator set (last enacted or initial if no changes ever made) with current stake.
|
/// Get current validator set (last enacted or initial if no changes ever made) with current stake.
|
||||||
function getValidators()
|
function getValidators()
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,7 @@ func (c *CliqueConfig) String() string {
|
||||||
type BorConfig struct {
|
type BorConfig struct {
|
||||||
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
|
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
|
||||||
ProducerDelay uint64 `json:"producerDelay"` // Number of seconds delay between two producer interval
|
ProducerDelay uint64 `json:"producerDelay"` // Number of seconds delay between two producer interval
|
||||||
Epoch uint64 `json:"epoch"` // Epoch length to proposer
|
Sprint uint64 `json:"sprint"` // Epoch length to proposer
|
||||||
ValidatorContract string `json:"validatorContract"` // Validator set contract
|
ValidatorContract string `json:"validatorContract"` // Validator set contract
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue