Files
zeekay 2faf6ed0df The wire is ZAP: no codec, no marshal, no parallel wallet
github.com/luxfi/codec is gone from this module — no import, no call, no
require. Nothing here marshals or unmarshals a transaction any more.

WHY IT BROKE. codec deleted its jsonrpc subpackage (1b67d6a, "delete dead
subpackages") while nine files imported it. It looked dead because the
PUBLISHED [email protected] still ships it, so only a workspace build — which
resolves codec from the local checkout — ever saw the break.

THE WIRE. A tx already carries its own ZAP encoding, so the codec was
re-deriving what the tx could hand over:
  txs.Codec.Marshal(txs.CodecVersion, &tx.Unsigned)  ->  tx.Unsigned.Bytes()
                                                          txs.UnsignedBytes(tx.Unsigned)
  txs.Codec.Marshal(txs.CodecVersion, tx)            ->  tx.Initialize()
  txs.Codec.Unmarshal(txBytes, &tx)                  ->  txs.Parse(txBytes)
  codec.Unmarshal(utxoBytes, &utxo)                  ->  utxo.ParseUTXO(...)
These are the same calls the node makes in txs.Tx.Sign. AddAllUTXOs no longer
takes a codec.Manager, and SortTransferableOutputs / state.Sort /
SortOperations no longer get one — upstream dropped the parameter.

PARALLEL IMPLEMENTATIONS, DELETED. wallet/chain/{p,x} carried a second copy of
the builder and signer beside the subpackages that already held them —
p/signer_visitor.go and p/signer/visitor.go were both 399 lines of the same
thing, and each defect had to be fixed twice. The flat files are gone; the
package keeps the facade it already had in wallet_exports.go, which re-exports
the subpackage. -4787 lines.

wallet/chain/c is deleted. Its whole reason to exist was a codec.Manager
scaffold for C-chain atomic-tx wire that nothing runs: NewWallet was passed
nil for it, and the builder was commented out. The two symbols anything used
move to where they are read — Account, a balance and a nonce off an EVM
client, and EVMKeychain, the geth-address half of a keychain. Wallet no longer
advertises a C() that only ever returned nil.

ALIGNED. XAssetID -> UTXOAssetID, matching runtime.Runtime and netrunner, which
already expected it. Keychain GetEth -> GetByEVM, EthAddrs -> EVMAddrs. warp
UnsignedMessage and Message collapsed into one flat Message, so a signature is
an Envelope rather than a second message type. netrunner's HealthResponse is
read as a field: the getter form is protobuf, behind //go:build grpc, and the
default build is ZAP-native.

VERIFIED on a machine with the native libraries: go build ./... exits 0 with no
codec require in go.mod, and go test ./... is 23 ok. Three packages still fail
to build their tests — profiles, wallet/keychain, wallet/primary — and all
three fail identically on the tree before this change: Consensus,
VerifyHybridSecp256k1MLDSA and isXChainNotEnabled are referenced by tests and
defined nowhere. evm failed there too and now passes.
2026-08-28 22:25:12 -07:00

319 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/constants"
warpPayload "github.com/luxfi/proto/p/warp/payload"
"github.com/luxfi/sdk/models"
"github.com/luxfi/geth/core/types"
"github.com/luxfi/ids"
"github.com/luxfi/sdk/contract"
"github.com/luxfi/sdk/validator"
"github.com/luxfi/sdk/validatormanager/txs"
"github.com/luxfi/sdk/validatormanager/validatormanagertypes"
warpMessage "github.com/luxfi/sdk/validatormanager/warp"
"github.com/luxfi/warp"
"github.com/luxfi/crypto"
)
type ACP99ValidatorManagerSettings struct {
Admin crypto.Address
ChainID [32]byte
ChurnPeriodSeconds uint64
MaximumChurnPercentage uint8
}
type ValidatorManagerSettings struct {
ChainID [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 crypto.Address
UptimeBlockchainID [32]byte
}
type NativeTokenValidatorManagerSettingsV2_0_0 struct {
Manager crypto.Address
MinimumStakeAmount *big.Int
MaximumStakeAmount *big.Int
MinimumStakeDuration uint64
MinimumDelegationFeeBips uint16
MaximumStakeMultiplier uint8
WeightToValueFactor *big.Int
RewardCalculator crypto.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")
ErrInvalidChainConversionID = fmt.Errorf("invalid chain 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,
"InvalidChainConversionID(bytes32,bytes32)": ErrInvalidChainConversionID,
"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 GetPChainChainToL1ConversionUnsignedMessage(
net models.Network,
chainID ids.ID,
managerBlockchainID ids.ID,
managerAddress crypto.Address,
convertChainValidators []*txs.ConvertNetworkToL1Validator,
) (*warp.Message, error) {
validators := []warpMessage.ChainToL1ConversionValidatorData{}
for _, convertChainValidator := range convertChainValidators {
validators = append(validators, warpMessage.ChainToL1ConversionValidatorData{
NodeID: convertChainValidator.NodeID[:],
BLSPublicKey: convertChainValidator.Signer.PublicKey,
Weight: convertChainValidator.Weight,
})
}
chainConversionData := warpMessage.ChainToL1ConversionData{
ChainID: chainID,
ManagerChainID: managerBlockchainID,
ManagerAddress: managerAddress.Bytes(),
Validators: validators,
}
chainConversionID, err := warpMessage.ChainToL1ConversionID(chainConversionData)
if err != nil {
return nil, err
}
addressedCallPayload, err := warpMessage.NewChainToL1Conversion(chainConversionID)
if err != nil {
return nil, err
}
chainConversionAddressedCall, err := warpPayload.NewAddressedCall(
nil,
addressedCallPayload.Bytes(),
)
if err != nil {
return nil, err
}
chainConversionUnsignedMessage, err := warp.NewMessage(
net.ID(),
constants.PlatformChainID,
chainConversionAddressedCall.Bytes(),
)
if err != nil {
return nil, err
}
return chainConversionUnsignedMessage, nil
}
// InitializeValidatorsSet calls poa manager validators set init method,
// passing to it the p-chain signed [chainConversionSignedMessage]
// to verify p-chain already processed the associated ConvertNetworkToL1Tx
func InitializeValidatorsSet(
rpcURL string,
managerAddress crypto.Address,
privateKey string,
chainID ids.ID,
managerBlockchainID ids.ID,
convertChainValidators []*txs.ConvertNetworkToL1Validator,
chainConversionSignedMessage *warp.Message,
) (*types.Transaction, *types.Receipt, error) {
type InitialValidator struct {
NodeID []byte
BlsPublicKey []byte
Weight uint64
}
type ChainConversionData struct {
ChainID [32]byte
ValidatorManagerBlockchainID [32]byte
ValidatorManagerAddress crypto.Address
InitialValidators []InitialValidator
}
validators := []InitialValidator{}
for _, convertChainValidator := range convertChainValidators {
validators = append(validators, InitialValidator{
NodeID: convertChainValidator.NodeID[:],
BlsPublicKey: convertChainValidator.Signer.PublicKey[:],
Weight: convertChainValidator.Weight,
})
}
chainConversionData := ChainConversionData{
ChainID: chainID,
ValidatorManagerBlockchainID: managerBlockchainID,
ValidatorManagerAddress: managerAddress,
InitialValidators: validators,
}
// No conversion needed - already using the right warp type
nodeWarpMsg := chainConversionSignedMessage
return contract.TxToMethodWithWarpMessage(
rpcURL,
false,
crypto.Address{},
privateKey,
managerAddress,
nodeWarpMsg,
big.NewInt(0),
"initialize validator set",
ErrorSignatureToError,
"initializeValidatorSet((bytes32,bytes32,address,[(bytes,bytes,uint64)]),uint32)",
chainConversionData,
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 crypto.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
}