mirror of https://github.com/poanetwork/gecko.git
362 lines
12 KiB
Go
362 lines
12 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() error {
|
|
switch {
|
|
case tx == nil:
|
|
return errNilTx
|
|
case !tx.senderID.IsZero():
|
|
return nil // Only verify the transaction once
|
|
case tx.id.IsZero():
|
|
return errInvalidID
|
|
case tx.NetworkID != tx.vm.Ctx.NetworkID:
|
|
return errWrongNetworkID
|
|
case tx.NodeID.IsZero():
|
|
return errInvalidID
|
|
case tx.Subnet.IsZero():
|
|
return errInvalidID
|
|
case tx.Wght == 0: // Ensure the validator has some weight
|
|
return errWeightTooSmall
|
|
case !crypto.IsSortedAndUniqueSECP2561RSigs(tx.ControlSigs):
|
|
return errSigsNotSorted
|
|
}
|
|
|
|
// Ensure staking length is not too short or long
|
|
stakingDuration := tx.Duration()
|
|
if stakingDuration < MinimumStakingDuration {
|
|
return errStakeTooShort
|
|
} else if stakingDuration > MaximumStakingDuration {
|
|
return errStakeTooLong
|
|
}
|
|
|
|
// Byte representation of the unsigned transaction
|
|
unsignedIntf := interface{}(&tx.UnsignedAddNonDefaultSubnetValidatorTx)
|
|
unsignedBytes, err := Codec.Marshal(&unsignedIntf)
|
|
if err != nil {
|
|
return 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 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 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(), error) {
|
|
// 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, 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, 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, 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, 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, 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, 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,
|
|
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, 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,
|
|
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,
|
|
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, fmt.Errorf("couldn't get current timestamp: %v", err)
|
|
}
|
|
validatorStartTime := tx.StartTime()
|
|
if !currentTimestamp.Before(validatorStartTime) {
|
|
return nil, nil, nil, nil, 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, 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, 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, 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, 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, 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, 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, fmt.Errorf("couldn't put current validators: %v", err)
|
|
}
|
|
if err := tx.vm.putAccount(onCommitDB, newAccount); err != nil {
|
|
return nil, nil, nil, nil, 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)
|
|
}
|