mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
424 lines
10 KiB
Go
424 lines
10 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
package validatormanager
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/luxfi/cli/v2/sdk/interchain"
|
|
|
|
"github.com/luxfi/cli/v2/pkg/application"
|
|
"github.com/luxfi/cli/v2/pkg/contract"
|
|
"github.com/luxfi/cli/v2/pkg/models"
|
|
"github.com/luxfi/cli/v2/pkg/utils"
|
|
"github.com/luxfi/cli/v2/pkg/ux"
|
|
"github.com/luxfi/cli/v2/sdk/evm"
|
|
"github.com/luxfi/cli/v2/sdk/validator"
|
|
"github.com/luxfi/cli/v2/sdk/validatormanager"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/node/v2/v2/utils/logging"
|
|
warp "github.com/luxfi/warp"
|
|
warpPayload "github.com/luxfi/warp/payload"
|
|
"github.com/luxfi/geth/core/types"
|
|
// TODO: Fix warp messages import
|
|
// "github.com/luxfi/evm/v2/warp/messages"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
)
|
|
|
|
func InitializeValidatorRemoval(
|
|
rpcURL string,
|
|
managerAddress common.Address,
|
|
generateRawTxOnly bool,
|
|
managerOwnerAddress common.Address,
|
|
privateKey string,
|
|
validationID ids.ID,
|
|
isPoS bool,
|
|
uptimeProofSignedMessage *warp.Message,
|
|
force bool,
|
|
useACP99 bool,
|
|
) (*types.Transaction, *types.Receipt, error) {
|
|
if isPoS {
|
|
if useACP99 {
|
|
if force {
|
|
return contract.TxToMethod(
|
|
rpcURL,
|
|
false,
|
|
common.Address{},
|
|
privateKey,
|
|
managerAddress,
|
|
big.NewInt(0),
|
|
"force POS validator removal",
|
|
validatormanager.ErrorSignatureToError,
|
|
"forceInitiateValidatorRemoval(bytes32,bool,uint32)",
|
|
validationID,
|
|
false, // no uptime proof if force
|
|
uint32(0),
|
|
)
|
|
}
|
|
// remove PoS validator with uptime proof
|
|
return contract.TxToMethodWithWarpMessage(
|
|
rpcURL,
|
|
false,
|
|
common.Address{},
|
|
privateKey,
|
|
managerAddress,
|
|
uptimeProofSignedMessage,
|
|
big.NewInt(0),
|
|
"POS validator removal with uptime proof",
|
|
validatormanager.ErrorSignatureToError,
|
|
"initiateValidatorRemoval(bytes32,bool,uint32)",
|
|
validationID,
|
|
true, // submit uptime proof
|
|
uint32(0),
|
|
)
|
|
}
|
|
if force {
|
|
return contract.TxToMethod(
|
|
rpcURL,
|
|
false,
|
|
common.Address{},
|
|
privateKey,
|
|
managerAddress,
|
|
big.NewInt(0),
|
|
"force POS validator removal",
|
|
validatormanager.ErrorSignatureToError,
|
|
"forceInitializeEndValidation(bytes32,bool,uint32)",
|
|
validationID,
|
|
false, // no uptime proof if force
|
|
uint32(0),
|
|
)
|
|
}
|
|
// remove PoS validator with uptime proof
|
|
return contract.TxToMethodWithWarpMessage(
|
|
rpcURL,
|
|
false,
|
|
common.Address{},
|
|
privateKey,
|
|
managerAddress,
|
|
uptimeProofSignedMessage,
|
|
big.NewInt(0),
|
|
"POS validator removal with uptime proof",
|
|
validatormanager.ErrorSignatureToError,
|
|
"initializeEndValidation(bytes32,bool,uint32)",
|
|
validationID,
|
|
true, // submit uptime proof
|
|
uint32(0),
|
|
)
|
|
}
|
|
// PoA case
|
|
if useACP99 {
|
|
return contract.TxToMethod(
|
|
rpcURL,
|
|
generateRawTxOnly,
|
|
managerOwnerAddress,
|
|
privateKey,
|
|
managerAddress,
|
|
big.NewInt(0),
|
|
"POA validator removal initialization",
|
|
validatormanager.ErrorSignatureToError,
|
|
"initiateValidatorRemoval(bytes32)",
|
|
validationID,
|
|
)
|
|
}
|
|
return contract.TxToMethod(
|
|
rpcURL,
|
|
generateRawTxOnly,
|
|
managerOwnerAddress,
|
|
privateKey,
|
|
managerAddress,
|
|
big.NewInt(0),
|
|
"POA validator removal initialization",
|
|
validatormanager.ErrorSignatureToError,
|
|
"initializeEndValidation(bytes32)",
|
|
validationID,
|
|
)
|
|
}
|
|
|
|
func GetUptimeProofMessage(
|
|
network models.Network,
|
|
aggregatorLogger logging.Logger,
|
|
aggregatorQuorumPercentage uint64,
|
|
subnetID ids.ID,
|
|
blockchainID ids.ID,
|
|
validationID ids.ID,
|
|
uptime uint64,
|
|
signatureAggregatorEndpoint string,
|
|
) (*warp.Message, error) {
|
|
uptimePayload, err := messages.NewValidatorUptime(validationID, uptime)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addressedCall, err := warpPayload.NewAddressedCall(nil, uptimePayload.Bytes())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
uptimeProofUnsignedMessage, err := warp.NewUnsignedMessage(
|
|
network.ID,
|
|
blockchainID,
|
|
addressedCall.Bytes(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
messageHexStr := hex.EncodeToString(uptimeProofUnsignedMessage.Bytes())
|
|
return interchain.SignMessage(aggregatorLogger, signatureAggregatorEndpoint, messageHexStr, "", subnetID.String(), aggregatorQuorumPercentage)
|
|
}
|
|
|
|
func InitValidatorRemoval(
|
|
ctx context.Context,
|
|
app *application.Lux,
|
|
network models.Network,
|
|
rpcURL string,
|
|
chainSpec contract.ChainSpec,
|
|
generateRawTxOnly bool,
|
|
ownerAddressStr string,
|
|
ownerPrivateKey string,
|
|
nodeID ids.NodeID,
|
|
aggregatorLogger logging.Logger,
|
|
isPoS bool,
|
|
uptimeSec uint64,
|
|
force bool,
|
|
validatorManagerAddressStr string,
|
|
useACP99 bool,
|
|
initiateTxHash string,
|
|
signatureAggregatorEndpoint string,
|
|
) (*warp.Message, ids.ID, *types.Transaction, error) {
|
|
subnetID, err := contract.GetSubnetID(
|
|
app,
|
|
network,
|
|
chainSpec,
|
|
)
|
|
if err != nil {
|
|
return nil, ids.Empty, nil, err
|
|
}
|
|
blockchainID, err := contract.GetBlockchainID(
|
|
app,
|
|
network,
|
|
chainSpec,
|
|
)
|
|
if err != nil {
|
|
return nil, ids.Empty, nil, err
|
|
}
|
|
managerAddress := common.HexToAddress(validatorManagerAddressStr)
|
|
ownerAddress := common.HexToAddress(ownerAddressStr)
|
|
validationID, err := validator.GetValidationID(
|
|
rpcURL,
|
|
managerAddress,
|
|
nodeID,
|
|
)
|
|
if err != nil {
|
|
return nil, ids.Empty, nil, err
|
|
}
|
|
if validationID == ids.Empty {
|
|
return nil, ids.Empty, nil, fmt.Errorf("node %s is not a L1 validator", nodeID)
|
|
}
|
|
|
|
var unsignedMessage *warp.UnsignedMessage
|
|
if initiateTxHash != "" {
|
|
unsignedMessage, err = GetL1ValidatorWeightMessageFromTx(
|
|
rpcURL,
|
|
validationID,
|
|
0,
|
|
initiateTxHash,
|
|
)
|
|
if err != nil {
|
|
return nil, ids.Empty, nil, err
|
|
}
|
|
}
|
|
|
|
var receipt *types.Receipt
|
|
if unsignedMessage == nil {
|
|
signedUptimeProof := &warp.Message{}
|
|
if isPoS {
|
|
if uptimeSec == 0 {
|
|
uptimeSec, err = utils.GetL1ValidatorUptimeSeconds(rpcURL, nodeID)
|
|
if err != nil {
|
|
return nil, ids.Empty, nil, evm.TransactionError(nil, err, "failure getting uptime data for nodeID: %s via %s ", nodeID, rpcURL)
|
|
}
|
|
}
|
|
ux.Logger.PrintToUser("Using uptime: %ds", uptimeSec)
|
|
signedUptimeProof, err = GetUptimeProofMessage(
|
|
network,
|
|
aggregatorLogger,
|
|
0,
|
|
subnetID,
|
|
blockchainID,
|
|
validationID,
|
|
uptimeSec,
|
|
signatureAggregatorEndpoint,
|
|
)
|
|
if err != nil {
|
|
return nil, ids.Empty, nil, evm.TransactionError(nil, err, "failure getting uptime proof")
|
|
}
|
|
}
|
|
var tx *types.Transaction
|
|
tx, receipt, err = InitializeValidatorRemoval(
|
|
rpcURL,
|
|
managerAddress,
|
|
generateRawTxOnly,
|
|
ownerAddress,
|
|
ownerPrivateKey,
|
|
validationID,
|
|
isPoS,
|
|
signedUptimeProof, // is empty for non-PoS
|
|
force,
|
|
useACP99,
|
|
)
|
|
switch {
|
|
case err != nil:
|
|
if !errors.Is(err, validatormanager.ErrInvalidValidatorStatus) {
|
|
return nil, ids.Empty, nil, evm.TransactionError(tx, err, "failure initializing validator removal")
|
|
}
|
|
ux.Logger.PrintToUser(logging.LightBlue.Wrap("The validator removal process was already initialized. Proceeding to the next step"))
|
|
case generateRawTxOnly:
|
|
return nil, ids.Empty, tx, nil
|
|
default:
|
|
ux.Logger.PrintToUser("Validator removal initialized. InitiateTxHash: %s", tx.Hash())
|
|
}
|
|
} else {
|
|
ux.Logger.PrintToUser(logging.LightBlue.Wrap("The validator removal process was already initialized. Proceeding to the next step"))
|
|
}
|
|
|
|
if receipt != nil {
|
|
unsignedMessage, err = evm.ExtractWarpMessageFromReceipt(receipt)
|
|
if err != nil {
|
|
return nil, ids.Empty, nil, err
|
|
}
|
|
}
|
|
|
|
var nonce uint64
|
|
if unsignedMessage == nil {
|
|
nonce, err = GetValidatorNonce(ctx, rpcURL, validationID)
|
|
if err != nil {
|
|
return nil, ids.Empty, nil, err
|
|
}
|
|
}
|
|
|
|
signedMsg, err := GetL1ValidatorWeightMessage(
|
|
network,
|
|
aggregatorLogger,
|
|
unsignedMessage,
|
|
subnetID,
|
|
blockchainID,
|
|
managerAddress,
|
|
validationID,
|
|
nonce,
|
|
0,
|
|
signatureAggregatorEndpoint,
|
|
)
|
|
return signedMsg, validationID, nil, err
|
|
}
|
|
|
|
func CompleteValidatorRemoval(
|
|
rpcURL string,
|
|
managerAddress common.Address,
|
|
generateRawTxOnly bool,
|
|
ownerAddress common.Address,
|
|
privateKey string, // not need to be owner atm
|
|
subnetValidatorRegistrationSignedMessage *warp.Message,
|
|
useACP99 bool,
|
|
) (*types.Transaction, *types.Receipt, error) {
|
|
if useACP99 {
|
|
return contract.TxToMethodWithWarpMessage(
|
|
rpcURL,
|
|
generateRawTxOnly,
|
|
ownerAddress,
|
|
privateKey,
|
|
managerAddress,
|
|
subnetValidatorRegistrationSignedMessage,
|
|
big.NewInt(0),
|
|
"complete validator removal",
|
|
validatormanager.ErrorSignatureToError,
|
|
"completeValidatorRemoval(uint32)",
|
|
uint32(0),
|
|
)
|
|
}
|
|
return contract.TxToMethodWithWarpMessage(
|
|
rpcURL,
|
|
generateRawTxOnly,
|
|
ownerAddress,
|
|
privateKey,
|
|
managerAddress,
|
|
subnetValidatorRegistrationSignedMessage,
|
|
big.NewInt(0),
|
|
"complete validator removal",
|
|
validatormanager.ErrorSignatureToError,
|
|
"completeEndValidation(uint32)",
|
|
uint32(0),
|
|
)
|
|
}
|
|
|
|
func FinishValidatorRemoval(
|
|
ctx context.Context,
|
|
app *application.Lux,
|
|
network models.Network,
|
|
rpcURL string,
|
|
chainSpec contract.ChainSpec,
|
|
generateRawTxOnly bool,
|
|
ownerAddressStr string,
|
|
privateKey string,
|
|
validationID ids.ID,
|
|
aggregatorLogger logging.Logger,
|
|
validatorManagerAddressStr string,
|
|
useACP99 bool,
|
|
signatureAggregatorEndpoint string,
|
|
) (*types.Transaction, error) {
|
|
managerAddress := common.HexToAddress(validatorManagerAddressStr)
|
|
subnetID, err := contract.GetSubnetID(
|
|
app,
|
|
network,
|
|
chainSpec,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
signedMessage, err := GetPChainL1ValidatorRegistrationMessage(
|
|
ctx,
|
|
network,
|
|
rpcURL,
|
|
aggregatorLogger,
|
|
0,
|
|
subnetID,
|
|
validationID,
|
|
false,
|
|
signatureAggregatorEndpoint,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if privateKey != "" {
|
|
if client, err := evm.GetClient(rpcURL); err != nil {
|
|
ux.Logger.RedXToUser("failure connecting to L1 to setup proposer VM: %s", err)
|
|
} else {
|
|
if err := client.SetupProposerVM(privateKey); err != nil {
|
|
ux.Logger.RedXToUser("failure setting proposer VM on L1: %w", err)
|
|
}
|
|
client.Close()
|
|
}
|
|
}
|
|
ownerAddress := common.HexToAddress(ownerAddressStr)
|
|
tx, _, err := CompleteValidatorRemoval(
|
|
rpcURL,
|
|
managerAddress,
|
|
generateRawTxOnly,
|
|
ownerAddress,
|
|
privateKey,
|
|
signedMessage,
|
|
useACP99,
|
|
)
|
|
if err != nil {
|
|
return nil, evm.TransactionError(tx, err, "failure completing validator removal")
|
|
}
|
|
if generateRawTxOnly {
|
|
return tx, nil
|
|
}
|
|
return nil, nil
|
|
}
|