mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
456 lines
11 KiB
Go
456 lines
11 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"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"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/interchain"
|
|
"github.com/luxfi/cli/v2/sdk/validator"
|
|
"github.com/luxfi/cli/v2/sdk/validatormanager"
|
|
"github.com/luxfi/ids"
|
|
luxdconstants "github.com/luxfi/node/v2/v2/utils/constants"
|
|
"github.com/luxfi/node/v2/v2/utils/logging"
|
|
warp "github.com/luxfi/warp"
|
|
warpMessage "github.com/luxfi/warp"
|
|
warpPayload "github.com/luxfi/warp/payload"
|
|
"github.com/luxfi/geth/core/types"
|
|
"github.com/luxfi/evm/v2/iface"
|
|
subnetEvmWarp "github.com/luxfi/evm/v2/precompile/contracts/warp"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
)
|
|
|
|
func InitializeValidatorWeightChange(
|
|
rpcURL string,
|
|
managerAddress common.Address,
|
|
generateRawTxOnly bool,
|
|
managerOwnerAddress common.Address,
|
|
privateKey string,
|
|
validationID ids.ID,
|
|
weight uint64,
|
|
) (*types.Transaction, *types.Receipt, error) {
|
|
return contract.TxToMethod(
|
|
rpcURL,
|
|
generateRawTxOnly,
|
|
managerOwnerAddress,
|
|
privateKey,
|
|
managerAddress,
|
|
big.NewInt(0),
|
|
"POA validator weight change initialization",
|
|
validatormanager.ErrorSignatureToError,
|
|
"initiateValidatorWeightUpdate(bytes32,uint64)",
|
|
validationID,
|
|
weight,
|
|
)
|
|
}
|
|
|
|
func InitValidatorWeightChange(
|
|
ctx context.Context,
|
|
printFunc func(msg string, args ...interface{}),
|
|
app *application.Lux,
|
|
network models.Network,
|
|
rpcURL string,
|
|
chainSpec contract.ChainSpec,
|
|
generateRawTxOnly bool,
|
|
ownerAddressStr string,
|
|
ownerPrivateKey string,
|
|
nodeID ids.NodeID,
|
|
aggregatorLogger logging.Logger,
|
|
validatorManagerAddressStr string,
|
|
weight uint64,
|
|
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,
|
|
weight,
|
|
initiateTxHash,
|
|
)
|
|
if err != nil {
|
|
return nil, ids.Empty, nil, err
|
|
}
|
|
}
|
|
|
|
if unsignedMessage == nil {
|
|
unsignedMessage, err = SearchForL1ValidatorWeightMessage(ctx, rpcURL, validationID, weight)
|
|
if err != nil {
|
|
printFunc(logging.Red.Wrap("Failure checking for warp messages of previous operations: %s. Proceeding."), err)
|
|
}
|
|
}
|
|
|
|
var receipt *types.Receipt
|
|
if unsignedMessage == nil {
|
|
var tx *types.Transaction
|
|
tx, receipt, err = InitializeValidatorWeightChange(
|
|
rpcURL,
|
|
managerAddress,
|
|
generateRawTxOnly,
|
|
ownerAddress,
|
|
ownerPrivateKey,
|
|
validationID,
|
|
weight,
|
|
)
|
|
switch {
|
|
case err != nil:
|
|
return nil, ids.Empty, nil, evm.TransactionError(tx, err, "failure initializing validator weight change")
|
|
case generateRawTxOnly:
|
|
return nil, ids.Empty, tx, nil
|
|
default:
|
|
ux.Logger.PrintToUser("Validator weight change initialized. InitiateTxHash: %s", tx.Hash())
|
|
}
|
|
} else {
|
|
printFunc(logging.LightBlue.Wrap("The validator weight change 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,
|
|
weight,
|
|
signatureAggregatorEndpoint,
|
|
)
|
|
return signedMsg, validationID, nil, err
|
|
}
|
|
|
|
func CompleteValidatorWeightChange(
|
|
rpcURL string,
|
|
managerAddress common.Address,
|
|
generateRawTxOnly bool,
|
|
ownerAddress common.Address,
|
|
privateKey string, // not need to be owner atm
|
|
pchainL1ValidatorRegistrationSignedMessage *warp.Message,
|
|
) (*types.Transaction, *types.Receipt, error) {
|
|
return contract.TxToMethodWithWarpMessage(
|
|
rpcURL,
|
|
generateRawTxOnly,
|
|
ownerAddress,
|
|
privateKey,
|
|
managerAddress,
|
|
pchainL1ValidatorRegistrationSignedMessage,
|
|
big.NewInt(0),
|
|
"complete poa validator weight change",
|
|
validatormanager.ErrorSignatureToError,
|
|
"completeValidatorWeightUpdate(uint32)",
|
|
uint32(0),
|
|
)
|
|
}
|
|
|
|
func FinishValidatorWeightChange(
|
|
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,
|
|
l1ValidatorRegistrationSignedMessage *warp.Message,
|
|
weight uint64,
|
|
signatureAggregatorEndpoint string,
|
|
) (*types.Transaction, error) {
|
|
managerAddress := common.HexToAddress(validatorManagerAddressStr)
|
|
subnetID, err := contract.GetSubnetID(
|
|
app,
|
|
network,
|
|
chainSpec,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var nonce uint64
|
|
if l1ValidatorRegistrationSignedMessage == nil {
|
|
nonce, err = GetValidatorNonce(ctx, rpcURL, validationID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
signedMessage, err := GetPChainL1ValidatorWeightMessage(
|
|
network,
|
|
aggregatorLogger,
|
|
0,
|
|
subnetID,
|
|
l1ValidatorRegistrationSignedMessage,
|
|
validationID,
|
|
nonce,
|
|
weight,
|
|
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 := CompleteValidatorWeightChange(
|
|
rpcURL,
|
|
managerAddress,
|
|
generateRawTxOnly,
|
|
ownerAddress,
|
|
privateKey,
|
|
signedMessage,
|
|
)
|
|
if err != nil {
|
|
return nil, evm.TransactionError(tx, err, "failure completing validator weight change")
|
|
}
|
|
if generateRawTxOnly {
|
|
return tx, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func GetL1ValidatorWeightMessage(
|
|
network models.Network,
|
|
aggregatorLogger logging.Logger,
|
|
// message is given
|
|
unsignedMessage *warp.UnsignedMessage,
|
|
subnetID ids.ID,
|
|
blockchainID ids.ID,
|
|
managerAddress common.Address,
|
|
validationID ids.ID,
|
|
nonce uint64,
|
|
weight uint64,
|
|
signatureAggregatorEndpoint string,
|
|
) (*warp.Message, error) {
|
|
if unsignedMessage == nil {
|
|
addressedCallPayload, err := warpMessage.NewL1ValidatorWeight(
|
|
validationID,
|
|
nonce,
|
|
weight,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addressedCall, err := warpPayload.NewAddressedCall(
|
|
managerAddress.Bytes(),
|
|
addressedCallPayload.Bytes(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
unsignedMessage, err = warp.NewUnsignedMessage(
|
|
network.ID,
|
|
blockchainID,
|
|
addressedCall.Bytes(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
messageHexStr := hex.EncodeToString(unsignedMessage.Bytes())
|
|
return interchain.SignMessage(aggregatorLogger, signatureAggregatorEndpoint, messageHexStr, "", subnetID.String(), 0)
|
|
}
|
|
|
|
func GetPChainL1ValidatorWeightMessage(
|
|
network models.Network,
|
|
aggregatorLogger logging.Logger,
|
|
aggregatorQuorumPercentage uint64,
|
|
subnetID ids.ID,
|
|
// message is given
|
|
l1SignedMessage *warp.Message,
|
|
// needed to generate full message contents
|
|
validationID ids.ID,
|
|
nonce uint64,
|
|
weight uint64,
|
|
signatureAggregatorEndpoint string,
|
|
) (*warp.Message, error) {
|
|
if l1SignedMessage != nil {
|
|
addressedCall, err := warpPayload.ParseAddressedCall(l1SignedMessage.UnsignedMessage.Payload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
weightMsg, err := warpMessage.ParseL1ValidatorWeight(addressedCall.Payload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
validationID = weightMsg.ValidationID
|
|
nonce = weightMsg.Nonce
|
|
weight = weightMsg.Weight
|
|
}
|
|
addressedCallPayload, err := warpMessage.NewL1ValidatorWeight(
|
|
validationID,
|
|
nonce,
|
|
weight,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addressedCall, err := warpPayload.NewAddressedCall(
|
|
nil,
|
|
addressedCallPayload.Bytes(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
unsignedMessage, err := warp.NewUnsignedMessage(
|
|
network.ID,
|
|
luxdconstants.PlatformChainID,
|
|
addressedCall.Bytes(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
messageHexStr := hex.EncodeToString(unsignedMessage.Bytes())
|
|
return interchain.SignMessage(aggregatorLogger, signatureAggregatorEndpoint, messageHexStr, "", subnetID.String(), aggregatorQuorumPercentage)
|
|
}
|
|
|
|
func GetL1ValidatorWeightMessageFromTx(
|
|
rpcURL string,
|
|
validationID ids.ID,
|
|
weight uint64,
|
|
txHash string,
|
|
) (*warp.UnsignedMessage, error) {
|
|
client, err := evm.GetClient(rpcURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
receipt, err := client.TransactionReceipt(common.HexToHash(txHash))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
msgs := evm.GetWarpMessagesFromLogs(receipt.Logs)
|
|
for _, msg := range msgs {
|
|
payload := msg.Payload
|
|
addressedCall, err := warpPayload.ParseAddressedCall(payload)
|
|
if err == nil {
|
|
weightMsg, err := warpMessage.ParseL1ValidatorWeight(addressedCall.Payload)
|
|
if err == nil {
|
|
if weightMsg.ValidationID == validationID && weightMsg.Weight == weight {
|
|
return msg, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("weight message not found on tx %s", txHash)
|
|
}
|
|
|
|
func SearchForL1ValidatorWeightMessage(
|
|
ctx context.Context,
|
|
rpcURL string,
|
|
validationID ids.ID,
|
|
weight uint64,
|
|
) (*warp.UnsignedMessage, error) {
|
|
maxBlocksToSearch := int64(5000000)
|
|
client, err := evm.GetClient(rpcURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
height, err := client.BlockNumber()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
maxBlock := int64(height)
|
|
minBlock := max(maxBlock-maxBlocksToSearch, 0)
|
|
blockStep := int64(5000)
|
|
for blockNumber := maxBlock; blockNumber >= minBlock; blockNumber -= blockStep {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
default:
|
|
}
|
|
fromBlock := big.NewInt(blockNumber - blockStep)
|
|
if fromBlock.Sign() < 0 {
|
|
fromBlock = big.NewInt(0)
|
|
}
|
|
toBlock := big.NewInt(blockNumber)
|
|
logs, err := client.FilterLogs(interfaces.FilterQuery{
|
|
FromBlock: fromBlock,
|
|
ToBlock: toBlock,
|
|
Addresses: []common.Address{subnetEvmWarp.Module.Address},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
msgs := evm.GetWarpMessagesFromLogs(utils.PointersSlice(logs))
|
|
for _, msg := range msgs {
|
|
payload := msg.Payload
|
|
addressedCall, err := warpPayload.ParseAddressedCall(payload)
|
|
if err == nil {
|
|
weightMsg, err := warpMessage.ParseL1ValidatorWeight(addressedCall.Payload)
|
|
if err == nil {
|
|
if weightMsg.ValidationID == validationID && weightMsg.Weight == weight {
|
|
return msg, nil
|
|
} else {
|
|
return nil, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|