Files

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
}