Merge branch 'master' into blacklist

pull/2172/head
Rongjian Lan 5 years ago committed by GitHub
commit cb69e9e251
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 85
      core/blockchain.go
  2. 10
      internal/hmyapi/apiv1/types.go
  3. 10
      internal/hmyapi/apiv2/types.go
  4. 35
      node/node_newblock.go
  5. 58
      staking/availability/measure.go
  6. 8
      staking/types/delegation.go
  7. 52
      staking/types/messages.go
  8. 2
      staking/types/transaction_test.go
  9. 82
      staking/types/validator.go

@ -1157,6 +1157,48 @@ func (bc *BlockChain) WriteBlockWithState(
//// Shard State and Validator Update
header := block.Header()
if len(header.ShardState()) > 0 {
// Write shard state for the new epoch
epoch := new(big.Int).Add(header.Epoch(), common.Big1)
shardState, err := block.Header().GetShardState()
if err == nil && shardState.Epoch != nil && bc.chainConfig.IsStaking(shardState.Epoch) {
// After staking, the epoch will be decided by the epoch in the shard state.
epoch = new(big.Int).Set(shardState.Epoch)
}
newShardState, err := bc.WriteShardStateBytes(batch, epoch, header.ShardState())
if err != nil {
header.Logger(utils.Logger()).Warn().Err(err).Msg("cannot store shard state")
return NonStatTy, err
}
// Find all the active validator addresses and store them in db
allActiveValidators := []common.Address{}
processed := make(map[common.Address]struct{})
for i := range newShardState.Shards {
shard := newShardState.Shards[i]
for j := range shard.Slots {
slot := shard.Slots[j]
if slot.EffectiveStake != nil { // For external validator
_, ok := processed[slot.EcdsaAddress]
if !ok {
processed[slot.EcdsaAddress] = struct{}{}
allActiveValidators = append(allActiveValidators, shard.Slots[j].EcdsaAddress)
}
}
}
}
// Update active validators
if err := bc.WriteActiveValidatorList(allActiveValidators); err != nil {
return NonStatTy, err
}
// Update snapshots for all validators
if err := bc.UpdateValidatorSnapshots(); err != nil {
return NonStatTy, err
}
}
// Do bookkeeping for new staking txns
for _, tx := range block.StakingTransactions() {
@ -1219,49 +1261,6 @@ func (bc *BlockChain) WriteBlockWithState(
}
}
if len(header.ShardState()) > 0 {
// Write shard state for the new epoch
epoch := new(big.Int).Add(header.Epoch(), common.Big1)
shardState, err := block.Header().GetShardState()
if err == nil && shardState.Epoch != nil && bc.chainConfig.IsStaking(shardState.Epoch) {
// After staking, the epoch will be decided by the epoch in the shard state.
epoch = new(big.Int).Set(shardState.Epoch)
}
newShardState, err := bc.WriteShardStateBytes(batch, epoch, header.ShardState())
if err != nil {
header.Logger(utils.Logger()).Warn().Err(err).Msg("cannot store shard state")
return NonStatTy, err
}
// Find all the active validator addresses and store them in db
allActiveValidators := []common.Address{}
processed := make(map[common.Address]struct{})
for i := range newShardState.Shards {
shard := newShardState.Shards[i]
for j := range shard.Slots {
slot := shard.Slots[j]
if slot.EffectiveStake != nil { // For external validator
_, ok := processed[slot.EcdsaAddress]
if !ok {
processed[slot.EcdsaAddress] = struct{}{}
allActiveValidators = append(allActiveValidators, shard.Slots[j].EcdsaAddress)
}
}
}
}
// Update active validators
if err := bc.WriteActiveValidatorList(allActiveValidators); err != nil {
return NonStatTy, err
}
// Update snapshots for all validators
if err := bc.UpdateValidatorSnapshots(); err != nil {
return NonStatTy, err
}
}
/////////////////////////// END
// If the total difficulty is higher than our known, add it to the canonical chain

@ -83,11 +83,11 @@ type HeaderInformation struct {
// RPCDelegation represents a particular delegation to a validator
type RPCDelegation struct {
ValidatorAddress string `json:"validator_address" yaml:"validator_address"`
DelegatorAddress string `json:"delegator_address" yaml:"delegator_address"`
Amount *big.Int `json:"amount" yaml:"amount"`
Reward *big.Int `json:"reward" yaml:"reward"`
Undelegations []RPCUndelegation `json:"Undelegations" yaml:"Undelegations"`
ValidatorAddress string `json:"validator_address"`
DelegatorAddress string `json:"delegator_address"`
Amount *big.Int `json:"amount"`
Reward *big.Int `json:"reward"`
Undelegations []RPCUndelegation `json:"Undelegations"`
}
// RPCUndelegation represents one undelegation entry

@ -83,11 +83,11 @@ type HeaderInformation struct {
// RPCDelegation represents a particular delegation to a validator
type RPCDelegation struct {
ValidatorAddress string `json:"validator_address" yaml:"validator_address"`
DelegatorAddress string `json:"delegator_address" yaml:"delegator_address"`
Amount *big.Int `json:"amount" yaml:"amount"`
Reward *big.Int `json:"reward" yaml:"reward"`
Undelegations []RPCUndelegation `json:"Undelegations" yaml:"Undelegations"`
ValidatorAddress string `json:"validator_address"`
DelegatorAddress string `json:"delegator_address"`
Amount *big.Int `json:"amount"`
Reward *big.Int `json:"reward"`
Undelegations []RPCUndelegation `json:"Undelegations"`
}
// RPCUndelegation represents one undelegation entry

@ -10,7 +10,6 @@ import (
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking/availability"
staking "github.com/harmony-one/harmony/staking/types"
)
@ -169,7 +168,7 @@ func (node *Node) proposeNewBlock() (*types.Block, error) {
}
// Bump up signers counts
state, header := node.Worker.GetCurrentState(), node.Blockchain().CurrentHeader()
_, header := node.Worker.GetCurrentState(), node.Blockchain().CurrentHeader()
if epoch := header.Epoch(); node.Blockchain().Config().IsStaking(epoch) {
if header.ShardID() == shard.BeaconChainShardID {
@ -192,22 +191,26 @@ func (node *Node) proposeNewBlock() (*types.Block, error) {
}
}
}
if err := availability.IncrementValidatorSigningCounts(
node.Blockchain(), header, header.ShardID(), state, processed,
); err != nil {
return nil, err
}
// kick out the inactive validators so they won't come up in the auction as possible
// candidates in the following call to SuperCommitteeForNextEpoch
if shard.Schedule.IsLastBlock(header.Number().Uint64()) {
if err := availability.SetInactiveUnavailableValidators(
node.Blockchain(), state, processed,
); err != nil {
return nil, err
}
}
// TODO(Edgar) Need to uncomment and fix, unknown why enabling
// this code causes merkle root verification issues
// if err := availability.IncrementValidatorSigningCounts(
// node.Blockchain(), header, header.ShardID(), state, processed,
// ); err != nil {
// return nil, err
// }
// // kick out the inactive validators so they won't come up in the auction as possible
// // candidates in the following call to SuperCommitteeForNextEpoch
// if shard.Schedule.IsLastBlock(header.Number().Uint64()) {
// fmt.Println("hit the last block condition")
// if err := availability.SetInactiveUnavailableValidators(
// node.Blockchain(), state, processed,
// ); err != nil {
// return nil, err
// }
// }
} else {
// TODO Handle shard chain
}

@ -67,7 +67,8 @@ func BlockSigners(
return payable, missing, nil
}
// BallotResult ..
// BallotResult returns
// (parentCommittee.Slots, payable, missing, err)
func BallotResult(
bc engine.ChainReader, header *block.Header, shardID uint32,
) (shard.SlotList, shard.SlotList, shard.SlotList, error) {
@ -97,25 +98,20 @@ func BallotResult(
)
}
payable, missing, err := BlockSigners(header.LastCommitBitmap(), parentCommittee)
payable, missing, err := BlockSigners(
header.LastCommitBitmap(), parentCommittee,
)
return parentCommittee.Slots, payable, missing, err
}
// IncrementValidatorSigningCounts ..
func IncrementValidatorSigningCounts(
chain engine.ChainReader, header *block.Header,
shardID uint32, state *state.DB, onlyConsider map[common.Address]struct{},
func bumpCount(
chain engine.ChainReader,
state *state.DB, onlyConsider map[common.Address]struct{},
signers shard.SlotList, didSign bool,
) error {
_, signers, _, err := BallotResult(chain, header, shardID)
if err != nil {
return err
}
epoch, one := chain.CurrentHeader().Epoch(), big.NewInt(1)
epoch := chain.CurrentHeader().Epoch()
for i := range signers {
addr := signers[i].EcdsaAddress
if _, ok := onlyConsider[addr]; !ok {
continue
}
@ -123,15 +119,38 @@ func IncrementValidatorSigningCounts(
if err != nil {
return err
}
wrapper.Snapshot.NumBlocksToSign.Add(wrapper.Snapshot.NumBlocksToSign, one)
wrapper.Snapshot.NumBlocksSigned.Add(wrapper.Snapshot.NumBlocksSigned, one)
wrapper.Snapshot.NumBlocksToSign.Add(
wrapper.Snapshot.NumBlocksToSign, common.Big1,
)
if didSign {
wrapper.Snapshot.NumBlocksSigned.Add(
wrapper.Snapshot.NumBlocksSigned, common.Big1,
)
}
wrapper.Snapshot.Epoch = epoch
if err := state.UpdateStakingInfo(addr, wrapper); err != nil {
return err
}
}
return nil
}
// IncrementValidatorSigningCounts ..
func IncrementValidatorSigningCounts(
chain engine.ChainReader, header *block.Header,
shardID uint32, state *state.DB, onlyConsider map[common.Address]struct{},
) error {
_, signers, missing, err := BallotResult(chain, header, shardID)
if err != nil {
return err
}
if err := bumpCount(chain, state, onlyConsider, signers, true); err != nil {
return err
}
if err := bumpCount(chain, state, onlyConsider, missing, false); err != nil {
return err
}
return nil
}
@ -144,13 +163,12 @@ func SetInactiveUnavailableValidators(
bc engine.ChainReader, state *state.DB,
onlyConsider map[common.Address]struct{},
) error {
addrs, err := bc.ReadActiveValidatorList()
if err != nil {
return err
}
one, now := big.NewInt(1), bc.CurrentHeader().Epoch()
now := bc.CurrentHeader().Epoch()
for i := range addrs {
if _, ok := onlyConsider[addrs[i]]; !ok {
@ -174,7 +192,7 @@ func SetInactiveUnavailableValidators(
snapSigned := snapshot.Snapshot.NumBlocksSigned
snapToSign := snapshot.Snapshot.NumBlocksToSign
if diff := new(big.Int).Sub(now, snapEpoch); diff.Cmp(one) != 0 {
if d := new(big.Int).Sub(now, snapEpoch); d.Cmp(common.Big1) != 0 {
return errors.Wrapf(
errValidatorEpochDeviation, "bc %s, snapshot %s",
now.String(), snapEpoch.String(),

@ -22,10 +22,10 @@ const (
// owned by one delegator, and is associated with the voting power of one
// validator.
type Delegation struct {
DelegatorAddress common.Address `json:"delegator_address" yaml:"delegator_address"`
Amount *big.Int `json:"amount" yaml:"amount"`
Reward *big.Int `json:"reward" yaml:"reward"`
Undelegations []Undelegation `json:"undelegations" yaml:"undelegations"`
DelegatorAddress common.Address `json:"delegator_address"`
Amount *big.Int `json:"amount"`
Reward *big.Int `json:"reward"`
Undelegations []Undelegation `json:"undelegations"`
}
// Undelegation represents one undelegation entry

@ -54,46 +54,46 @@ func (d Directive) String() string {
// CreateValidator - type for creating a new validator
type CreateValidator struct {
ValidatorAddress common.Address `json:"validator_address" yaml:"validator_address"`
Description *Description `json:"description" yaml:"description"`
CommissionRates `json:"commission" yaml:"commission"`
MinSelfDelegation *big.Int `json:"min_self_delegation" yaml:"min_self_delegation"`
MaxTotalDelegation *big.Int `json:"max_total_delegation" yaml:"max_total_delegation"`
SlotPubKeys []shard.BlsPublicKey `json:"slot_pub_keys" yaml:"slot_pub_keys"`
SlotKeySigs []shard.BlsSignature `json:"slot_key_sigs" yaml:"slot_key_sigs"`
Amount *big.Int `json:"amount" yaml:"amount"`
ValidatorAddress common.Address `json:"validator_address"`
Description `json:"description"`
CommissionRates `json:"commission"`
MinSelfDelegation *big.Int `json:"min_self_delegation"`
MaxTotalDelegation *big.Int `json:"max_total_delegation"`
SlotPubKeys []shard.BlsPublicKey `json:"slot_pub_keys"`
SlotKeySigs []shard.BlsSignature `json:"slot_key_sigs"`
Amount *big.Int `json:"amount"`
}
// EditValidator - type for edit existing validator
type EditValidator struct {
ValidatorAddress common.Address `json:"validator_address" yaml:"validator_address" rlp:"nil"`
Description *Description `json:"description" yaml:"description" rlp:"nil"`
CommissionRate *numeric.Dec `json:"commission_rate" yaml:"commission_rate" rlp:"nil"`
MinSelfDelegation *big.Int `json:"min_self_delegation" yaml:"min_self_delegation" rlp:"nil"`
MaxTotalDelegation *big.Int `json:"max_total_delegation" yaml:"max_total_delegation" rlp:"nil"`
SlotKeyToRemove *shard.BlsPublicKey `json:"slot_key_to_remove" yaml:"slot_key_to_remove" rlp:"nil"`
SlotKeyToAdd *shard.BlsPublicKey `json:"slot_key_to_add" yaml:"slot_key_to_add" rlp:"nil"`
SlotKeyToAddSig shard.BlsSignature `json:"slot_key_to_add_sig" yaml:"slot_key_to_add_sig" rlp:"nil"`
ValidatorAddress common.Address `json:"validator_address"`
Description `json:"description"`
CommissionRate *numeric.Dec `json:"commission_rate" rlp:"nil"`
MinSelfDelegation *big.Int `json:"min_self_delegation" rlp:"nil"`
MaxTotalDelegation *big.Int `json:"max_total_delegation" rlp:"nil"`
SlotKeyToRemove *shard.BlsPublicKey `json:"slot_key_to_remove" rlp:"nil"`
SlotKeyToAdd *shard.BlsPublicKey `json:"slot_key_to_add" rlp:"nil"`
SlotKeyToAddSig *shard.BlsSignature `json:"slot_key_to_add_sig" rlp:"nil"`
Active *bool `json:"active" rlp:"nil"`
}
// Delegate - type for delegating to a validator
type Delegate struct {
DelegatorAddress common.Address `json:"delegator_address" yaml:"delegator_address"`
ValidatorAddress common.Address `json:"validator_address" yaml:"validator_address"`
Amount *big.Int `json:"amount" yaml:"amount" rlp:"nil"`
DelegatorAddress common.Address `json:"delegator_address"`
ValidatorAddress common.Address `json:"validator_address"`
Amount *big.Int `json:"amount"`
}
// Undelegate - type for removing delegation responsibility
type Undelegate struct {
DelegatorAddress common.Address `json:"delegator_address" yaml:"delegator_address" rlp:"nil"`
ValidatorAddress common.Address `json:"validator_address" yaml:"validator_address" rlp:"nil"`
Amount *big.Int `json:"amount" yaml:"amount" rlp:"nil"`
DelegatorAddress common.Address `json:"delegator_address"`
ValidatorAddress common.Address `json:"validator_address"`
Amount *big.Int `json:"amount"`
}
// CollectRewards - type for collecting token rewards
type CollectRewards struct {
DelegatorAddress common.Address `json:"delegator_address" yaml:"delegator_address"`
DelegatorAddress common.Address `json:"delegator_address"`
}
// Type of CreateValidator
@ -124,16 +124,14 @@ func (v CollectRewards) Type() Directive {
// Copy deep copy of the interface
func (v CreateValidator) Copy() StakeMsg {
v1 := v
desc := *v.Description
v1.Description = &desc
v1.Description = v.Description
return v1
}
// Copy deep copy of the interface
func (v EditValidator) Copy() StakeMsg {
v1 := v
desc := *v.Description
v1.Description = &desc
v1.Description = v.Description
return v1
}

@ -32,7 +32,7 @@ func CreateTestNewTransaction() (*StakingTransaction, error) {
maxRate, _ := numeric.NewDecFromStr("1")
maxChangeRate, _ := numeric.NewDecFromStr("0.5")
return DirectiveCreateValidator, CreateValidator{
Description: &Description{
Description: Description{
Name: "SuperHero",
Identity: "YouWouldNotKnow",
Website: "Secret Website",

@ -19,8 +19,8 @@ import (
// Define validator staking related const
const (
MaxNameLength = 70
MaxIdentityLength = 3000
MaxNameLength = 140
MaxIdentityLength = 140
MaxWebsiteLength = 140
MaxSecurityContactLength = 140
MaxDetailsLength = 280
@ -37,13 +37,14 @@ var (
errInvalidComissionRate = errors.New("commission rate, change rate and max rate should be within 0-100 percent")
errNeedAtLeastOneSlotKey = errors.New("need at least one slot key")
errBLSKeysNotMatchSigs = errors.New("bls keys and corresponding signatures could not be verified")
errNilMinSelfDelegation = errors.New("nil min self delegation")
errNilMinSelfDelegation = errors.New("MinSelfDelegation can not be nil")
errNilMaxTotalDelegation = errors.New("MaxTotalDelegation can not be nil")
)
// ValidatorWrapper contains validator and its delegation information
type ValidatorWrapper struct {
Validator `json:"validator" yaml:"validator" rlp:"nil"`
Delegations []Delegation `json:"delegations" yaml:"delegations" rlp:"nil"`
Validator `json:"validator"`
Delegations []Delegation `json:"delegations"`
Snapshot struct {
Epoch *big.Int
@ -180,6 +181,10 @@ func (w *ValidatorWrapper) SanityCheck() error {
return errNilMinSelfDelegation
}
if w.Validator.MaxTotalDelegation == nil {
return errNilMaxTotalDelegation
}
// MinSelfDelegation must be >= 1 ONE
if w.Validator.MinSelfDelegation.Cmp(big.NewInt(denominations.One)) < 0 {
return errors.Wrapf(
@ -203,8 +208,6 @@ func (w *ValidatorWrapper) SanityCheck() error {
}
}
// Only enforce rules on MaxTotalDelegation is it's > 0; 0 means no limit for max total delegation
if w.Validator.MaxTotalDelegation != nil && w.Validator.MaxTotalDelegation.Cmp(big.NewInt(0)) > 0 {
// MaxTotalDelegation must not be less than MinSelfDelegation
if w.Validator.MaxTotalDelegation.Cmp(w.Validator.MinSelfDelegation) < 0 {
return errors.Wrapf(
@ -225,7 +228,6 @@ func (w *ValidatorWrapper) SanityCheck() error {
w.Validator.MaxTotalDelegation.String(),
)
}
}
if w.Validator.Rate.LT(zeroPercent) || w.Validator.Rate.GT(hundredPercent) {
return errors.Wrapf(
@ -262,11 +264,11 @@ func (w *ValidatorWrapper) SanityCheck() error {
// Description - some possible IRL connections
type Description struct {
Name string `json:"name" yaml:"name"` // name
Identity string `json:"identity" yaml:"identity"` // optional identity signature (ex. UPort or Keybase)
Website string `json:"website" yaml:"website"` // optional website link
SecurityContact string `json:"security_contact" yaml:"security_contact"` // optional security contact info
Details string `json:"details" yaml:"details"` // optional details
Name string `json:"name"` // name
Identity string `json:"identity"` // optional identity signature (ex. UPort or Keybase)
Website string `json:"website"` // optional website link
SecurityContact string `json:"security_contact"` // optional security contact info
Details string `json:"details"` // optional details
}
// MarshalValidator marshals the validator object
@ -281,27 +283,27 @@ func UnmarshalValidator(by []byte) (*Validator, error) {
return decoded, err
}
// NewDescription returns a new Description with the provided values.
func NewDescription(name, identity, website, securityContact, details string) Description {
return Description{
Name: name,
Identity: identity,
Website: website,
SecurityContact: securityContact,
Details: details,
// UpdateDescription returns a new Description object with d1 as the base and the fields that's not empty in d2 updated
// accordingly. An error is returned if the resulting description fields have invalid length.
func UpdateDescription(d1, d2 Description) (Description, error) {
newDesc := d1
if d2.Name != "" {
newDesc.Name = d2.Name
}
if d2.Identity != "" {
newDesc.Identity = d2.Identity
}
if d2.Website != "" {
newDesc.Website = d2.Website
}
if d2.SecurityContact != "" {
newDesc.SecurityContact = d2.SecurityContact
}
if d2.Details != "" {
newDesc.Details = d2.Details
}
// UpdateDescription updates the fields of a given description. An error is
// returned if the resulting description contains an invalid length.
func UpdateDescription(d2 *Description) (Description, error) {
return NewDescription(
d2.Name,
d2.Identity,
d2.Website,
d2.SecurityContact,
d2.Details,
).EnsureLength()
return newDesc.EnsureLength()
}
// EnsureLength ensures the length of a validator's description.
@ -343,7 +345,7 @@ func verifyBLSKeys(pubKeys []shard.BlsPublicKey, pubKeySigs []shard.BlsSignature
}
for i := 0; i < len(pubKeys); i++ {
if err := verifyBLSKey(pubKeys[i], pubKeySigs[i]); err != nil {
if err := verifyBLSKey(&pubKeys[i], &pubKeySigs[i]); err != nil {
return err
}
}
@ -351,7 +353,7 @@ func verifyBLSKeys(pubKeys []shard.BlsPublicKey, pubKeySigs []shard.BlsSignature
return nil
}
func verifyBLSKey(pubKey shard.BlsPublicKey, pubKeySig shard.BlsSignature) error {
func verifyBLSKey(pubKey *shard.BlsPublicKey, pubKeySig *shard.BlsSignature) error {
if len(pubKeySig) == 0 {
return errBLSKeysNotMatchSigs
}
@ -377,7 +379,7 @@ func verifyBLSKey(pubKey shard.BlsPublicKey, pubKeySig shard.BlsSignature) error
// CreateValidatorFromNewMsg creates validator from NewValidator message
func CreateValidatorFromNewMsg(val *CreateValidator, blockNum *big.Int) (*Validator, error) {
desc, err := UpdateDescription(val.Description)
desc, err := val.Description.EnsureLength()
if err != nil {
return nil, err
}
@ -388,7 +390,6 @@ func CreateValidatorFromNewMsg(val *CreateValidator, blockNum *big.Int) (*Valida
return nil, err
}
// TODO: a new validator should have a minimum of 1 token as self delegation, and that should be added as a delegation entry here.
v := Validator{
val.ValidatorAddress, pubKeys,
new(big.Int), val.MinSelfDelegation, val.MaxTotalDelegation, true,
@ -398,29 +399,26 @@ func CreateValidatorFromNewMsg(val *CreateValidator, blockNum *big.Int) (*Valida
}
// UpdateValidatorFromEditMsg updates validator from EditValidator message
// TODO check the validity of the fields of edit message
func UpdateValidatorFromEditMsg(validator *Validator, edit *EditValidator) error {
if validator.Address != edit.ValidatorAddress {
return errAddressNotMatch
}
if edit.Description != nil {
desc, err := UpdateDescription(edit.Description)
desc, err := UpdateDescription(validator.Description, edit.Description)
if err != nil {
return err
}
validator.Description = desc
}
if edit.CommissionRate != nil {
validator.Rate = *edit.CommissionRate
}
if edit.MinSelfDelegation != nil && edit.MinSelfDelegation.Cmp(big.NewInt(0)) != 0 {
if edit.MinSelfDelegation != nil && edit.MinSelfDelegation.Sign() != 0 {
validator.MinSelfDelegation = edit.MinSelfDelegation
}
if edit.MaxTotalDelegation != nil && edit.MaxTotalDelegation.Cmp(big.NewInt(0)) != 0 {
if edit.MaxTotalDelegation != nil && edit.MaxTotalDelegation.Sign() != 0 {
validator.MaxTotalDelegation = edit.MaxTotalDelegation
}
@ -447,7 +445,7 @@ func UpdateValidatorFromEditMsg(validator *Validator, edit *EditValidator) error
}
}
if !found {
if err := verifyBLSKey(*edit.SlotKeyToAdd, edit.SlotKeyToAddSig); err != nil {
if err := verifyBLSKey(edit.SlotKeyToAdd, edit.SlotKeyToAddSig); err != nil {
return err
}
validator.SlotPubKeys = append(validator.SlotPubKeys, *edit.SlotKeyToAdd)

Loading…
Cancel
Save