gecko/vms/platformvm/add_nondefault_subnet_valid...

360 lines
13 KiB
Go

// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package platformvm
import (
"errors"
"fmt"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/versiondb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/validators"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/hashing"
)
var (
errSigsNotSorted = errors.New("control signatures not sorted")
errWrongNumberOfSignatures = errors.New("wrong number of signatures")
errDSValidatorSubset = errors.New("all subnets must be a subset of the default subnet")
)
// UnsignedAddNonDefaultSubnetValidatorTx is an unsigned addNonDefaultSubnetValidatorTx
type UnsignedAddNonDefaultSubnetValidatorTx struct {
// The validator
SubnetValidator `serialize:"true"`
// ID of the network
NetworkID uint32 `serialize:"true"`
// Next unused nonce of the account paying the tx fee
Nonce uint64 `serialize:"true"`
}
// addNonDefaultSubnetValidatorTx is a transaction that, if it is in a ProposeAddValidator block that
// is accepted and followed by a Commit block, adds a validator to the pending validator set of a subnet
// other than the default subnet.
// (That is, the validator in the tx will validate at some point in the future.)
// The transaction fee will be paid from the account whose ID is [Sigs[0].Address()]
type addNonDefaultSubnetValidatorTx struct {
UnsignedAddNonDefaultSubnetValidatorTx `serialize:"true"`
// When a subnet is created, it specifies a set of public keys ("control keys") such
// that in order to add a validator to the subnet, a tx must be signed with
// a certain threshold of those keys
// Each element of ControlSigs is the signature of one of those keys
ControlSigs [][crypto.SECP256K1RSigLen]byte `serialize:"true"`
// PayerSig is the signature of the public key whose corresponding account pays
// the tx fee for this tx
// ie the account with ID == [public key].Address() pays the tx fee
PayerSig [crypto.SECP256K1RSigLen]byte `serialize:"true"`
vm *VM
id ids.ID
controlIDs []ids.ShortID
senderID ids.ShortID
// Byte representation of the signed transaction
bytes []byte
}
// initialize [tx]
func (tx *addNonDefaultSubnetValidatorTx) initialize(vm *VM) error {
bytes, err := Codec.Marshal(tx) // byte representation of the signed transaction
if err != nil {
return err
}
tx.vm = vm
tx.bytes = bytes
tx.id = ids.NewID(hashing.ComputeHash256Array(bytes))
return nil
}
func (tx *addNonDefaultSubnetValidatorTx) ID() ids.ID { return tx.id }
// SyntacticVerify return nil iff [tx] is valid
// If [tx] is valid, sets [tx.accountID]
func (tx *addNonDefaultSubnetValidatorTx) SyntacticVerify() TxError {
switch {
case tx == nil:
return tempError{errNilTx}
case !tx.senderID.IsZero():
return nil // Only verify the transaction once
case tx.id.IsZero():
return tempError{errInvalidID}
case tx.NetworkID != tx.vm.Ctx.NetworkID:
return permError{errWrongNetworkID}
case tx.NodeID.IsZero():
return tempError{errInvalidID}
case tx.Subnet.IsZero():
return tempError{errInvalidID}
case tx.Wght == 0: // Ensure the validator has some weight
return permError{errWeightTooSmall}
case !crypto.IsSortedAndUniqueSECP2561RSigs(tx.ControlSigs):
return permError{errSigsNotSorted}
}
// Ensure staking length is not too short or long
stakingDuration := tx.Duration()
if stakingDuration < MinimumStakingDuration {
return permError{errStakeTooShort}
} else if stakingDuration > MaximumStakingDuration {
return permError{errStakeTooLong}
}
// Byte representation of the unsigned transaction
unsignedIntf := interface{}(&tx.UnsignedAddNonDefaultSubnetValidatorTx)
unsignedBytes, err := Codec.Marshal(&unsignedIntf)
if err != nil {
return permError{err}
}
unsignedBytesHash := hashing.ComputeHash256(unsignedBytes)
tx.controlIDs = make([]ids.ShortID, len(tx.ControlSigs))
// recover control signatures
for i, sig := range tx.ControlSigs {
key, err := tx.vm.factory.RecoverHashPublicKey(unsignedBytesHash, sig[:])
if err != nil {
return permError{err}
}
tx.controlIDs[i] = key.Address()
}
// get account to pay tx fee from
key, err := tx.vm.factory.RecoverHashPublicKey(unsignedBytesHash, tx.PayerSig[:])
if err != nil {
return permError{err}
}
tx.senderID = key.Address()
return nil
}
// getDefaultSubnetStaker ...
func (h *EventHeap) getDefaultSubnetStaker(id ids.ShortID) (*addDefaultSubnetValidatorTx, error) {
for _, txIntf := range h.Txs {
tx, ok := txIntf.(*addDefaultSubnetValidatorTx)
if !ok {
continue
}
if id.Equals(tx.NodeID) {
return tx, nil
}
}
return nil, errors.New("couldn't find validator in the default subnet")
}
// SemanticVerify this transaction is valid.
func (tx *addNonDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (*versiondb.Database, *versiondb.Database, func(), func(), TxError) {
// Ensure tx is syntactically valid
if err := tx.SyntacticVerify(); err != nil {
return nil, nil, nil, nil, err
}
// Get info about the subnet we're adding a validator to
subnets, err := tx.vm.getSubnets(db)
if err != nil {
return nil, nil, nil, nil, permError{err}
}
var subnet *CreateSubnetTx
for _, sn := range subnets {
if sn.id.Equals(tx.SubnetID()) {
subnet = sn
break
}
}
if subnet == nil {
return nil, nil, nil, nil, permError{fmt.Errorf("there is no subnet with ID %s", tx.SubnetID())}
}
// Ensure the sigs on [tx] are valid
if len(tx.ControlSigs) != int(subnet.Threshold) {
return nil, nil, nil, nil, permError{fmt.Errorf("expected tx to have %d control sigs but has %d", subnet.Threshold, len(tx.ControlSigs))}
}
if !crypto.IsSortedAndUniqueSECP2561RSigs(tx.ControlSigs) {
return nil, nil, nil, nil, permError{errors.New("control signatures aren't sorted")}
}
controlKeys := ids.ShortSet{}
controlKeys.Add(subnet.ControlKeys...)
for _, controlID := range tx.controlIDs {
if !controlKeys.Contains(controlID) {
return nil, nil, nil, nil, permError{errors.New("tx has control signature from key not in subnet's ControlKeys")}
}
}
// Ensure that the period this validator validates the specified subnet is a subnet of the time they validate the default subnet
// First, see if they're currently validating the default subnet
currentDSValidators, err := tx.vm.getCurrentValidators(db, DefaultSubnetID)
if err != nil {
return nil, nil, nil, nil, permError{fmt.Errorf("couldn't get current validators of default subnet: %v", err)}
}
if dsValidator, err := currentDSValidators.getDefaultSubnetStaker(tx.NodeID); err == nil {
if !tx.DurationValidator.BoundedBy(dsValidator.StartTime(), dsValidator.EndTime()) {
return nil, nil, nil, nil,
permError{fmt.Errorf("time validating subnet [%v, %v] not subset of time validating default subnet [%v, %v]",
tx.DurationValidator.StartTime(), tx.DurationValidator.EndTime(),
dsValidator.StartTime(), dsValidator.EndTime())}
}
} else {
// They aren't currently validating the default subnet.
// See if they will validate the default subnet in the future.
pendingDSValidators, err := tx.vm.getPendingValidators(db, DefaultSubnetID)
if err != nil {
return nil, nil, nil, nil, permError{fmt.Errorf("couldn't get pending validators of default subnet: %v", err)}
}
dsValidator, err := pendingDSValidators.getDefaultSubnetStaker(tx.NodeID)
if err != nil {
return nil, nil, nil, nil,
permError{fmt.Errorf("validator would not be validating default subnet while validating non-default subnet")}
}
if !tx.DurationValidator.BoundedBy(dsValidator.StartTime(), dsValidator.EndTime()) {
return nil, nil, nil, nil,
permError{fmt.Errorf("time validating subnet [%v, %v] not subset of time validating default subnet [%v, %v]",
tx.DurationValidator.StartTime(), tx.DurationValidator.EndTime(),
dsValidator.StartTime(), dsValidator.EndTime())}
}
}
// Ensure the proposed validator starts after the current timestamp
currentTimestamp, err := tx.vm.getTimestamp(db)
if err != nil {
return nil, nil, nil, nil, permError{fmt.Errorf("couldn't get current timestamp: %v", err)}
}
validatorStartTime := tx.StartTime()
if !currentTimestamp.Before(validatorStartTime) {
return nil, nil, nil, nil, permError{fmt.Errorf("chain timestamp (%s) not before validator's start time (%s)",
currentTimestamp,
validatorStartTime)}
}
// Get the account that is paying the transaction fee and, if the proposal is to add a validator
// to the default subnet, providing the staked $AVA.
// The ID of this account is the address associated with the public key that signed this tx
accountID := tx.senderID
account, err := tx.vm.getAccount(db, accountID)
if err != nil {
return nil, nil, nil, nil, permError{errDBAccount}
}
// The account if this block's proposal is committed and the validator is added
// to the pending validator set. (Increase the account's nonce; decrease its balance.)
newAccount, err := account.Remove(0, tx.Nonce) // Remove also removes the fee
if err != nil {
return nil, nil, nil, nil, permError{err}
}
// Ensure the proposed validator is not already a validator of the specified subnet
currentEvents, err := tx.vm.getCurrentValidators(db, tx.Subnet)
if err != nil {
return nil, nil, nil, nil, permError{fmt.Errorf("couldn't get current validators of subnet %s: %v", tx.Subnet, err)}
}
currentValidators := validators.NewSet()
currentValidators.Set(tx.vm.getValidators(currentEvents))
if currentValidators.Contains(tx.NodeID) {
return nil, nil, nil, nil, permError{fmt.Errorf("validator with ID %s already in the current validator set for subnet with ID %s",
tx.NodeID,
tx.Subnet)}
}
// Ensure the proposed validator is not already slated to validate for the specified subnet
pendingEvents, err := tx.vm.getPendingValidators(db, tx.Subnet)
if err != nil {
return nil, nil, nil, nil, permError{fmt.Errorf("couldn't get pending validators of subnet %s: %v", tx.Subnet, err)}
}
pendingValidators := validators.NewSet()
pendingValidators.Set(tx.vm.getValidators(pendingEvents))
if pendingValidators.Contains(tx.NodeID) {
return nil, nil, nil, nil, permError{fmt.Errorf("validator with ID %s already in the pending validator set for subnet with ID %s",
tx.NodeID,
tx.Subnet)}
}
pendingEvents.Add(tx) // add validator to set of pending validators
// If this proposal is committed, update the pending validator set to include the validator,
// update the validator's account by removing the staked $AVA
onCommitDB := versiondb.New(db)
if err := tx.vm.putPendingValidators(onCommitDB, pendingEvents, tx.Subnet); err != nil {
return nil, nil, nil, nil, permError{fmt.Errorf("couldn't put current validators: %v", err)}
}
if err := tx.vm.putAccount(onCommitDB, newAccount); err != nil {
return nil, nil, nil, nil, permError{fmt.Errorf("couldn't put account: %v", err)}
}
// If this proposal is aborted, chain state doesn't change
onAbortDB := versiondb.New(db)
return onCommitDB, onAbortDB, nil, nil, nil
}
// InitiallyPrefersCommit returns true if the proposed validators start time is
// after the current wall clock time,
func (tx *addNonDefaultSubnetValidatorTx) InitiallyPrefersCommit() bool {
return tx.StartTime().After(tx.vm.clock.Time())
}
func (vm *VM) newAddNonDefaultSubnetValidatorTx(
nonce,
weight,
startTime,
endTime uint64,
nodeID ids.ShortID,
subnetID ids.ID,
networkID uint32,
controlKeys []*crypto.PrivateKeySECP256K1R,
payerKey *crypto.PrivateKeySECP256K1R,
) (*addNonDefaultSubnetValidatorTx, error) {
tx := &addNonDefaultSubnetValidatorTx{
UnsignedAddNonDefaultSubnetValidatorTx: UnsignedAddNonDefaultSubnetValidatorTx{
SubnetValidator: SubnetValidator{
DurationValidator: DurationValidator{
Validator: Validator{
NodeID: nodeID,
Wght: weight,
},
Start: startTime,
End: endTime,
},
Subnet: subnetID,
},
NetworkID: networkID,
Nonce: nonce,
},
}
unsignedIntf := interface{}(&tx.UnsignedAddNonDefaultSubnetValidatorTx)
unsignedBytes, err := Codec.Marshal(&unsignedIntf) // byte repr. of unsigned tx
if err != nil {
return nil, err
}
unsignedHash := hashing.ComputeHash256(unsignedBytes)
// Sign this tx with each control key
tx.ControlSigs = make([][crypto.SECP256K1RSigLen]byte, len(controlKeys))
for i, key := range controlKeys {
sig, err := key.SignHash(unsignedHash)
if err != nil {
return nil, err
}
// tx.ControlSigs[i] is type [65]byte but sig is type []byte
// so we have to do the below
copy(tx.ControlSigs[i][:], sig)
}
crypto.SortSECP2561RSigs(tx.ControlSigs)
// Sign this tx with the key of the tx fee payer
sig, err := payerKey.SignHash(unsignedHash)
if err != nil {
return nil, err
}
copy(tx.PayerSig[:], sig)
return tx, tx.initialize(vm)
}