Files
zeekay e274211816 cli: switch primary addValidator to AddValidatorTx (LP-018)
Validators validate networks, chains live on networks. Under LP-018
sovereign-L1, AddValidatorTx is the universal add-validator tx for any
primary network. AddChainValidatorTx is legacy and kept for one
release cycle for wire compat with pre-LP-018 binaries.

- pkg/chain/public.go: createAddChainValidatorTx → createAddValidatorTx;
  IssueAddChainValidatorTx → IssueAddValidatorTx; signature is
  (*txs.Validator, *secp256k1fx.OutputOwners, uint32, primary.Wallet).
- cmd/primarycmd/add_validator.go: deployer.AddValidator now passes
  rewardsOwner=nil (defaults to wallet's first address) and the
  delegationFee flag value as delegationShares.
- pkg/txutils/{auth.go,info.go}: prose updated to network-centric
  language; legacy AddChainValidatorTx case branches retained but
  annotated as deprecated.
2026-06-08 16:03:28 -07:00

103 lines
4.0 KiB
Go

// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package txutils provides transaction utilities for creating, signing, and managing transactions.
package txutils
import (
"fmt"
"github.com/luxfi/crypto/secp256k1"
"github.com/luxfi/proto/p/txs"
"github.com/luxfi/utxo/secp256k1fx"
"github.com/luxfi/vm/components/verify"
)
// GetAuthSigners returns all network/chain auth addresses required to sign a tx.
// It reads the auth-indices on the tx and resolves them against controlKeys
// (the network's or chain's owner key set, as returned by GetOwners).
//
// Expected tx.Unsigned types are the chain-owner-authorized forms:
// CreateChainTx, ConvertNetworkToL1Tx, plus the legacy AddChainValidatorTx
// and RemoveChainValidatorTx (kept here for one release cycle while the
// sovereign-L1 model phases in; new validator adds use AddValidatorTx which
// is NOT chain-owner-authorized and therefore never appears here).
// controlKeys must be in the same order as in the owning tx (per GetOwners).
func GetAuthSigners(tx *txs.Tx, controlKeys []string) ([]string, error) {
unsignedTx := tx.Unsigned
var chainAuth verify.Verifiable
switch unsignedTx := unsignedTx.(type) {
case *txs.RemoveChainValidatorTx:
chainAuth = unsignedTx.ChainAuth
case *txs.AddChainValidatorTx:
// Deprecated path — see AddValidatorTx for the network-level form.
chainAuth = unsignedTx.ChainAuth
case *txs.CreateChainTx:
chainAuth = unsignedTx.ChainAuth
case *txs.ConvertNetworkToL1Tx:
chainAuth = unsignedTx.ChainAuth
default:
return nil, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx)
}
chainInput, ok := chainAuth.(*secp256k1fx.Input)
if !ok {
return nil, fmt.Errorf("expected chainAuth of type *secp256k1fx.Input, got %T", chainAuth)
}
authSigners := []string{}
for _, sigIndex := range chainInput.SigIndices {
if sigIndex >= uint32(len(controlKeys)) { //nolint:gosec // G115: Length is small, safe conversion
return nil, fmt.Errorf("signer index %d exceeds number of control keys", sigIndex)
}
authSigners = append(authSigners, controlKeys[sigIndex])
}
return authSigners, nil
}
// GetRemainingSigners returns chain auth addresses that have not yet signed a given tx.
// It verifies that all creds in tx.Creds (except the last one) are fully signed,
// and computes remaining signers by iterating the last cred in tx.Creds.
// If the tx is fully signed, returns empty slice.
//
// controlKeys must be in the same order as in the chain creation tx (as obtained by GetOwners).
func GetRemainingSigners(tx *txs.Tx, controlKeys []string) ([]string, []string, error) {
authSigners, err := GetAuthSigners(tx, controlKeys)
if err != nil {
return nil, nil, err
}
emptySig := [secp256k1.SignatureLen]byte{}
// we should have at least 1 cred for output owners and 1 cred for chain auth
if len(tx.Creds) < 2 {
return nil, nil, fmt.Errorf("expected tx.Creds of len 2, got %d", len(tx.Creds))
}
// signatures for output owners should be filled (all creds except last one)
for credIndex := range tx.Creds[:len(tx.Creds)-1] {
cred, ok := tx.Creds[credIndex].(*secp256k1fx.Credential)
if !ok {
return nil, nil, fmt.Errorf("expected cred to be of type *secp256k1fx.Credential, got %T", tx.Creds[credIndex])
}
for i, sig := range cred.Sigs {
if sig == emptySig {
return nil, nil, fmt.Errorf("expected funding sig %d of cred %d to be filled", i, credIndex)
}
}
}
// signatures for chain auth (last cred)
cred, ok := tx.Creds[len(tx.Creds)-1].(*secp256k1fx.Credential)
if !ok {
return nil, nil, fmt.Errorf("expected cred to be of type *secp256k1fx.Credential, got %T", tx.Creds[1])
}
if len(cred.Sigs) != len(authSigners) {
return nil, nil, fmt.Errorf("expected number of cred's signatures %d to equal number of auth signers %d",
len(cred.Sigs),
len(authSigners),
)
}
remainingSigners := []string{}
for i, sig := range cred.Sigs {
if sig == emptySig {
remainingSigners = append(remainingSigners, authSigners[i])
}
}
return authSigners, remainingSigners, nil
}