mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
316 lines
14 KiB
Go
316 lines
14 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package validatormanager
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/luxfi/cli/v2/sdk/network"
|
|
luxdconstants "github.com/luxfi/node/v2/v2/utils/constants"
|
|
warpMessage "github.com/luxfi/warp"
|
|
warpPayload "github.com/luxfi/warp/payload"
|
|
|
|
"github.com/luxfi/cli/v2/pkg/contract"
|
|
"github.com/luxfi/cli/v2/sdk/validator"
|
|
"github.com/luxfi/cli/v2/sdk/validatormanager/validatormanagertypes"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/node/v2/v2/vms/platformvm/txs"
|
|
"github.com/luxfi/warp"
|
|
"github.com/luxfi/geth/core/types"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
)
|
|
|
|
type ACP99ValidatorManagerSettings struct {
|
|
Admin common.Address
|
|
SubnetID [32]byte
|
|
ChurnPeriodSeconds uint64
|
|
MaximumChurnPercentage uint8
|
|
}
|
|
|
|
type ValidatorManagerSettings struct {
|
|
SubnetID [32]byte
|
|
ChurnPeriodSeconds uint64
|
|
MaximumChurnPercentage uint8
|
|
}
|
|
|
|
type NativeTokenValidatorManagerSettingsV1_0_0 struct {
|
|
BaseSettings ValidatorManagerSettings
|
|
MinimumStakeAmount *big.Int
|
|
MaximumStakeAmount *big.Int
|
|
MinimumStakeDuration uint64
|
|
MinimumDelegationFeeBips uint16
|
|
MaximumStakeMultiplier uint8
|
|
WeightToValueFactor *big.Int
|
|
RewardCalculator common.Address
|
|
UptimeBlockchainID [32]byte
|
|
}
|
|
|
|
type NativeTokenValidatorManagerSettingsV2_0_0 struct {
|
|
Manager common.Address
|
|
MinimumStakeAmount *big.Int
|
|
MaximumStakeAmount *big.Int
|
|
MinimumStakeDuration uint64
|
|
MinimumDelegationFeeBips uint16
|
|
MaximumStakeMultiplier uint8
|
|
WeightToValueFactor *big.Int
|
|
RewardCalculator common.Address
|
|
UptimeBlockchainID [32]byte
|
|
}
|
|
|
|
const (
|
|
ValidatorMessagesContractAddress = "0x9C00629cE712B0255b17A4a657171Acd15720B8C"
|
|
ValidatorContractAddress = "0x0C0DEBA5E0000000000000000000000000000000"
|
|
ValidatorProxyContractAddress = "0x0FEEDC0DE0000000000000000000000000000000"
|
|
ValidatorProxyAdminContractAddress = "0xA0AFFE1234567890aBcDEF1234567890AbCdEf34"
|
|
SpecializationProxyContractAddress = "0x100C0DE1C0FFEE00000000000000000000000000"
|
|
SpecializationProxyAdminContractAddress = "0x97A35a4A2A8a56256de7A32160819c7B3F4C9DA6"
|
|
RewardCalculatorAddress = "0x0DEADC0DE0000000000000000000000000000000"
|
|
|
|
DefaultPoSMinimumStakeAmount = 1
|
|
DefaultPoSMaximumStakeAmount = 1000
|
|
DefaultPoSMinimumStakeDuration = 100
|
|
DefaultPoSDMinimumDelegationFee = 1
|
|
DefaultPoSMaximumStakeMultiplier = 1
|
|
DefaultPoSWeightToValueFactor = 1
|
|
)
|
|
|
|
var (
|
|
ErrDelegatorIneligibleForRewards = fmt.Errorf("delegator ineligible for rewards")
|
|
ErrInvalidBLSPublicKey = fmt.Errorf("invalid BLS public key")
|
|
ErrAlreadyInitialized = fmt.Errorf("the contract is already initialized")
|
|
ErrInvalidMaximumChurnPercentage = fmt.Errorf("unvalid churn percentage")
|
|
ErrInvalidValidationID = fmt.Errorf("invalid validation id")
|
|
ErrInvalidValidatorStatus = fmt.Errorf("invalid validator status")
|
|
ErrMaxChurnRateExceeded = fmt.Errorf("max churn rate exceeded")
|
|
ErrInvalidInitializationStatus = fmt.Errorf("validators set already initialized")
|
|
ErrInvalidValidatorManagerBlockchainID = fmt.Errorf("invalid validator manager blockchain ID")
|
|
ErrInvalidValidatorManagerAddress = fmt.Errorf("invalid validator manager address")
|
|
ErrNodeAlreadyRegistered = fmt.Errorf("node already registered")
|
|
ErrInvalidSubnetConversionID = fmt.Errorf("invalid subnet conversion id")
|
|
ErrInvalidRegistrationExpiry = fmt.Errorf("invalid registration expiry")
|
|
ErrInvalidBLSKeyLength = fmt.Errorf("invalid BLS key length")
|
|
ErrInvalidNodeID = fmt.Errorf("invalid node id")
|
|
ErrInvalidWarpMessage = fmt.Errorf("invalid warp message")
|
|
ErrInvalidWarpSourceChainID = fmt.Errorf("invalid wapr source chain ID")
|
|
ErrInvalidWarpOriginSenderAddress = fmt.Errorf("invalid warp origin sender address")
|
|
ErrInvalidCodecID = fmt.Errorf("invalid codec ID")
|
|
ErrInvalidConversionID = fmt.Errorf("invalid conversion ID")
|
|
ErrInvalidDelegationFee = fmt.Errorf("invalid delegation fee")
|
|
ErrInvalidDelegationID = fmt.Errorf("invalid delegation ID")
|
|
ErrInvalidDelegatorStatus = fmt.Errorf("invalid delegator status")
|
|
ErrInvalidMessageLength = fmt.Errorf("invalid message length")
|
|
ErrInvalidMessageType = fmt.Errorf("invalid message type")
|
|
ErrInvalidMinStakeDuration = fmt.Errorf("invalid min stake duration")
|
|
ErrInvalidNonce = fmt.Errorf("invalid nonce")
|
|
ErrInvalidPChainOwnerThreshold = fmt.Errorf("invalid pchain owner threshold")
|
|
ErrInvalidStakeAmount = fmt.Errorf("invalid stake amount")
|
|
ErrInvalidStakeMultiplier = fmt.Errorf("invalid stake multiplier")
|
|
ErrInvalidTokenAddress = fmt.Errorf("invalid token address")
|
|
ErrInvalidTotalWeight = fmt.Errorf("invalid total weight")
|
|
ErrMaxWeightExceeded = fmt.Errorf("max weight exceeded")
|
|
ErrMinStakeDurationNotPassed = fmt.Errorf("min stake duration not passed")
|
|
ErrPChainOwnerAddressesNotSorted = fmt.Errorf("pchain owner addresses not sorted")
|
|
ErrUnauthorizedOwner = fmt.Errorf("unauthorized owner")
|
|
ErrUnexpectedRegistrationStatus = fmt.Errorf("unexpected registration status")
|
|
ErrValidatorIneligibleForRewards = fmt.Errorf("validator ineligible for rewards")
|
|
ErrValidatorNotPoS = fmt.Errorf("validator not PoS")
|
|
ErrZeroWeightToValueFactor = fmt.Errorf("zero weight to value factor")
|
|
ErrInvalidOwner = fmt.Errorf("invalid proxy or validator owner")
|
|
ErrorSignatureToError = map[string]error{
|
|
"InvalidInitialization()": ErrAlreadyInitialized,
|
|
"InvalidMaximumChurnPercentage(uint8)": ErrInvalidMaximumChurnPercentage,
|
|
"InvalidValidationID(bytes32)": ErrInvalidValidationID,
|
|
"InvalidValidatorStatus(uint8)": ErrInvalidValidatorStatus,
|
|
"MaxChurnRateExceeded(uint64)": ErrMaxChurnRateExceeded,
|
|
"InvalidInitializationStatus()": ErrInvalidInitializationStatus,
|
|
"InvalidValidatorManagerBlockchainID(bytes32)": ErrInvalidValidatorManagerBlockchainID,
|
|
"InvalidValidatorManagerAddress(address)": ErrInvalidValidatorManagerAddress,
|
|
"NodeAlreadyRegistered(bytes)": ErrNodeAlreadyRegistered,
|
|
"InvalidSubnetConversionID(bytes32,bytes32)": ErrInvalidSubnetConversionID,
|
|
"InvalidRegistrationExpiry(uint64)": ErrInvalidRegistrationExpiry,
|
|
"InvalidBLSKeyLength(uint256)": ErrInvalidBLSKeyLength,
|
|
"InvalidNodeID(bytes)": ErrInvalidNodeID,
|
|
"InvalidWarpMessage()": ErrInvalidWarpMessage,
|
|
"InvalidWarpSourceChainID(bytes32)": ErrInvalidWarpSourceChainID,
|
|
"InvalidWarpOriginSenderAddress(address)": ErrInvalidWarpOriginSenderAddress,
|
|
"DelegatorIneligibleForRewards(bytes32)": ErrDelegatorIneligibleForRewards,
|
|
"InvalidBLSPublicKey()": ErrInvalidBLSPublicKey,
|
|
"InvalidCodecID(uint32)": ErrInvalidCodecID,
|
|
"InvalidConversionID(bytes32,bytes32)": ErrInvalidConversionID,
|
|
"InvalidDelegationFee(uint16)": ErrInvalidDelegationFee,
|
|
"InvalidDelegationID(bytes32)": ErrInvalidDelegationID,
|
|
"InvalidDelegatorStatus(DelegatorStatus)": ErrInvalidDelegatorStatus,
|
|
"InvalidMessageLength(uint32,uint32)": ErrInvalidMessageLength,
|
|
"InvalidMessageType()": ErrInvalidMessageType,
|
|
"InvalidMinStakeDuration(uint64)": ErrInvalidMinStakeDuration,
|
|
"InvalidNonce(uint64)": ErrInvalidNonce,
|
|
"InvalidPChainOwnerThreshold(uint256,uint256)": ErrInvalidPChainOwnerThreshold,
|
|
"InvalidStakeAmount(uint256)": ErrInvalidStakeAmount,
|
|
"InvalidStakeMultiplier(uint8)": ErrInvalidStakeMultiplier,
|
|
"InvalidTokenAddress(address)": ErrInvalidTokenAddress,
|
|
"InvalidTotalWeight(uint256)": ErrInvalidTotalWeight,
|
|
"MaxWeightExceeded(uint64)": ErrMaxWeightExceeded,
|
|
"MinStakeDurationNotPassed(uint64)": ErrMinStakeDurationNotPassed,
|
|
"PChainOwnerAddressesNotSorted()": ErrPChainOwnerAddressesNotSorted,
|
|
"UnauthorizedOwner(address)": ErrUnauthorizedOwner,
|
|
"UnexpectedRegistrationStatus(bool)": ErrUnexpectedRegistrationStatus,
|
|
"ValidatorIneligibleForRewards(bytes32)": ErrValidatorIneligibleForRewards,
|
|
"ValidatorNotPoS(bytes32)": ErrValidatorNotPoS,
|
|
"ZeroWeightToValueFactor()": ErrZeroWeightToValueFactor,
|
|
"OwnableInvalidOwner(address)": ErrInvalidOwner,
|
|
"OwnableUnauthorizedAccount(address)": ErrUnauthorizedOwner,
|
|
}
|
|
)
|
|
|
|
type PoSParams struct {
|
|
MinimumStakeAmount *big.Int
|
|
MaximumStakeAmount *big.Int
|
|
MinimumStakeDuration uint64
|
|
MinimumDelegationFee uint16
|
|
MaximumStakeMultiplier uint8
|
|
WeightToValueFactor *big.Int
|
|
RewardCalculatorAddress string
|
|
UptimeBlockchainID ids.ID
|
|
}
|
|
|
|
func (p PoSParams) Verify() error {
|
|
if p.MinimumStakeAmount.Cmp(big.NewInt(0)) < 0 {
|
|
return fmt.Errorf("minimum stake amount cannot be negative")
|
|
}
|
|
if p.MaximumStakeAmount.Cmp(big.NewInt(0)) < 0 {
|
|
return fmt.Errorf("maximum stake amount cannot be negative")
|
|
}
|
|
if p.MaximumStakeAmount.Cmp(p.MinimumStakeAmount) < 0 {
|
|
return fmt.Errorf("maximum stake amount cannot be less than minimum stake amount")
|
|
}
|
|
if p.WeightToValueFactor.Cmp(big.NewInt(0)) < 0 {
|
|
return fmt.Errorf("weight to value factor cannot be negative")
|
|
}
|
|
if p.RewardCalculatorAddress == "" {
|
|
return fmt.Errorf("reward calculator address cannot be empty")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func GetPChainSubnetToL1ConversionUnsignedMessage(
|
|
network network.Network,
|
|
subnetID ids.ID,
|
|
managerBlockchainID ids.ID,
|
|
managerAddress common.Address,
|
|
convertSubnetValidators []*txs.ConvertSubnetToL1Validator,
|
|
) (*warp.UnsignedMessage, error) {
|
|
validators := []warpMessage.SubnetToL1ConversionValidatorData{}
|
|
for _, convertSubnetValidator := range convertSubnetValidators {
|
|
validators = append(validators, warpMessage.SubnetToL1ConversionValidatorData{
|
|
NodeID: convertSubnetValidator.NodeID[:],
|
|
BLSPublicKey: convertSubnetValidator.Signer.PublicKey,
|
|
Weight: convertSubnetValidator.Weight,
|
|
})
|
|
}
|
|
subnetConversionData := warpMessage.SubnetToL1ConversionData{
|
|
SubnetID: subnetID,
|
|
ManagerChainID: managerBlockchainID,
|
|
ManagerAddress: managerAddress.Bytes(),
|
|
Validators: validators,
|
|
}
|
|
subnetConversionID, err := warpMessage.SubnetToL1ConversionID(subnetConversionData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addressedCallPayload, err := warpMessage.NewSubnetToL1Conversion(subnetConversionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
subnetConversionAddressedCall, err := warpPayload.NewAddressedCall(
|
|
nil,
|
|
addressedCallPayload.Bytes(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
subnetConversionUnsignedMessage, err := warp.NewUnsignedMessage(
|
|
network.ID,
|
|
luxdconstants.PlatformChainID,
|
|
subnetConversionAddressedCall.Bytes(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return subnetConversionUnsignedMessage, nil
|
|
}
|
|
|
|
// InitializeValidatorsSet calls poa manager validators set init method,
|
|
// passing to it the p-chain signed [subnetConversionSignedMessage]
|
|
// to verify p-chain already processed the associated ConvertSubnetToL1Tx
|
|
func InitializeValidatorsSet(
|
|
rpcURL string,
|
|
managerAddress common.Address,
|
|
privateKey string,
|
|
subnetID ids.ID,
|
|
managerBlockchainID ids.ID,
|
|
convertSubnetValidators []*txs.ConvertSubnetToL1Validator,
|
|
subnetConversionSignedMessage *warp.Message,
|
|
) (*types.Transaction, *types.Receipt, error) {
|
|
type InitialValidator struct {
|
|
NodeID []byte
|
|
BlsPublicKey []byte
|
|
Weight uint64
|
|
}
|
|
type SubnetConversionData struct {
|
|
SubnetID [32]byte
|
|
ValidatorManagerBlockchainID [32]byte
|
|
ValidatorManagerAddress common.Address
|
|
InitialValidators []InitialValidator
|
|
}
|
|
validators := []InitialValidator{}
|
|
for _, convertSubnetValidator := range convertSubnetValidators {
|
|
validators = append(validators, InitialValidator{
|
|
NodeID: convertSubnetValidator.NodeID[:],
|
|
BlsPublicKey: convertSubnetValidator.Signer.PublicKey[:],
|
|
Weight: convertSubnetValidator.Weight,
|
|
})
|
|
}
|
|
subnetConversionData := SubnetConversionData{
|
|
SubnetID: subnetID,
|
|
ValidatorManagerBlockchainID: managerBlockchainID,
|
|
ValidatorManagerAddress: managerAddress,
|
|
InitialValidators: validators,
|
|
}
|
|
return contract.TxToMethodWithWarpMessage(
|
|
rpcURL,
|
|
false,
|
|
common.Address{},
|
|
privateKey,
|
|
managerAddress,
|
|
subnetConversionSignedMessage,
|
|
big.NewInt(0),
|
|
"initialize validator set",
|
|
ErrorSignatureToError,
|
|
"initializeValidatorSet((bytes32,bytes32,address,[(bytes,bytes,uint64)]),uint32)",
|
|
subnetConversionData,
|
|
uint32(0),
|
|
)
|
|
}
|
|
|
|
// GetValidatorManagerType returns validatormanagertypes.ProofOfAuthority if validator manager is verified to be Proof of Authority
|
|
// If validator manager is verified to be Proof of Stake, returns validatormanagertypes.ProofOfStake
|
|
// In other cases, returns validatormanagertypes.UndefinedValidatorManagement
|
|
func GetValidatorManagerType(
|
|
rpcURL string,
|
|
managerAddress common.Address,
|
|
) validatormanagertypes.ValidatorManagementType {
|
|
// verify it is PoS
|
|
if _, err := PoSWeightToValue(rpcURL, managerAddress, 0); err == nil {
|
|
return validatormanagertypes.ProofOfStake
|
|
}
|
|
// verify it is PoA
|
|
if _, err := validator.GetValidationID(rpcURL, managerAddress, ids.EmptyNodeID); err == nil {
|
|
return validatormanagertypes.ProofOfAuthority
|
|
}
|
|
return validatormanagertypes.UndefinedValidatorManagement
|
|
}
|