2020-03-10 12:20:34 -07:00
|
|
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
|
|
|
|
package platformvm
|
|
|
|
|
|
|
|
import (
|
|
|
|
"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/utils/crypto"
|
|
|
|
"github.com/ava-labs/gecko/utils/hashing"
|
|
|
|
)
|
|
|
|
|
|
|
|
// UnsignedAddDefaultSubnetDelegatorTx is an unsigned addDefaultSubnetDelegatorTx
|
|
|
|
type UnsignedAddDefaultSubnetDelegatorTx struct {
|
|
|
|
DurationValidator `serialize:"true"`
|
|
|
|
NetworkID uint32 `serialize:"true"`
|
|
|
|
Nonce uint64 `serialize:"true"`
|
|
|
|
Destination ids.ShortID `serialize:"true"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// addDefaultSubnetDelegatorTx is a transaction that, if it is in a
|
|
|
|
// ProposalBlock that is accepted and followed by a Commit block, adds a
|
|
|
|
// delegator to the pending validator set of the default subnet. (That is, the
|
|
|
|
// validator in the tx will have their weight increase at some point in the
|
|
|
|
// future.) The transaction fee will be paid from the account who signed the
|
|
|
|
// transaction.
|
|
|
|
type addDefaultSubnetDelegatorTx struct {
|
|
|
|
UnsignedAddDefaultSubnetDelegatorTx `serialize:"true"`
|
|
|
|
|
|
|
|
// Sig 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
|
|
|
|
Sig [crypto.SECP256K1RSigLen]byte `serialize:"true"`
|
|
|
|
|
|
|
|
vm *VM
|
|
|
|
id ids.ID
|
|
|
|
senderID ids.ShortID
|
|
|
|
|
|
|
|
// Byte representation of the signed transaction
|
|
|
|
bytes []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
// initialize [tx]
|
|
|
|
func (tx *addDefaultSubnetDelegatorTx) initialize(vm *VM) error {
|
|
|
|
tx.vm = vm
|
|
|
|
bytes, err := Codec.Marshal(tx) // byte representation of the signed transaction
|
|
|
|
tx.bytes = bytes
|
|
|
|
tx.id = ids.NewID(hashing.ComputeHash256Array(bytes))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tx *addDefaultSubnetDelegatorTx) ID() ids.ID { return tx.id }
|
|
|
|
|
|
|
|
// SyntacticVerify return nil iff [tx] is valid
|
|
|
|
// If [tx] is valid, sets [tx.accountID]
|
2020-06-08 15:57:05 -07:00
|
|
|
func (tx *addDefaultSubnetDelegatorTx) SyntacticVerify() TxError {
|
2020-03-10 12:20:34 -07:00
|
|
|
switch {
|
|
|
|
case tx == nil:
|
2020-06-08 15:57:05 -07:00
|
|
|
return tempError{errNilTx}
|
2020-03-10 12:20:34 -07:00
|
|
|
case !tx.senderID.IsZero():
|
|
|
|
return nil // Only verify the transaction once
|
|
|
|
case tx.id.IsZero():
|
2020-06-08 15:57:05 -07:00
|
|
|
return tempError{errInvalidID}
|
2020-03-10 12:20:34 -07:00
|
|
|
case tx.NetworkID != tx.vm.Ctx.NetworkID:
|
2020-06-08 15:57:05 -07:00
|
|
|
return permError{errWrongNetworkID}
|
2020-03-10 12:20:34 -07:00
|
|
|
case tx.NodeID.IsZero():
|
2020-06-08 15:57:05 -07:00
|
|
|
return tempError{errInvalidID}
|
2020-03-10 12:20:34 -07:00
|
|
|
case tx.Wght < MinimumStakeAmount: // Ensure validator is staking at least the minimum amount
|
2020-06-08 15:57:05 -07:00
|
|
|
return permError{errWeightTooSmall}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure staking length is not too short or long
|
|
|
|
stakingDuration := tx.Duration()
|
|
|
|
if stakingDuration < MinimumStakingDuration {
|
2020-06-08 15:57:05 -07:00
|
|
|
return permError{errStakeTooShort}
|
2020-03-10 12:20:34 -07:00
|
|
|
} else if stakingDuration > MaximumStakingDuration {
|
2020-06-08 15:57:05 -07:00
|
|
|
return permError{errStakeTooLong}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
unsignedIntf := interface{}(&tx.UnsignedAddDefaultSubnetDelegatorTx)
|
|
|
|
// Byte representation of the unsigned transaction
|
|
|
|
unsignedBytes, err := Codec.Marshal(&unsignedIntf)
|
|
|
|
if err != nil {
|
2020-06-08 15:57:05 -07:00
|
|
|
return permError{err}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// get account to pay tx fee from
|
|
|
|
key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:])
|
|
|
|
if err != nil {
|
2020-06-08 15:57:05 -07:00
|
|
|
return permError{err}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
tx.senderID = key.Address()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SemanticVerify this transaction is valid.
|
2020-06-08 15:57:05 -07:00
|
|
|
func (tx *addDefaultSubnetDelegatorTx) SemanticVerify(db database.Database) (*versiondb.Database, *versiondb.Database, func(), func(), TxError) {
|
2020-03-10 12:20:34 -07:00
|
|
|
if err := tx.SyntacticVerify(); err != nil {
|
|
|
|
return nil, nil, nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the proposed validator starts after the current timestamp
|
|
|
|
currentTimestamp, err := tx.vm.getTimestamp(db)
|
|
|
|
if err != nil {
|
2020-06-08 15:57:05 -07:00
|
|
|
return nil, nil, nil, nil, permError{err}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
validatorStartTime := tx.StartTime()
|
|
|
|
if !currentTimestamp.Before(validatorStartTime) {
|
2020-06-08 15:57:05 -07:00
|
|
|
return nil, nil, nil, nil, permError{fmt.Errorf("chain timestamp (%s) not before validator's start time (%s)",
|
2020-03-10 12:20:34 -07:00
|
|
|
currentTimestamp,
|
2020-06-08 15:57:05 -07:00
|
|
|
validatorStartTime)}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
2020-06-08 15:57:05 -07:00
|
|
|
// 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.
|
2020-03-10 12:20:34 -07:00
|
|
|
accountID := tx.senderID
|
|
|
|
account, err := tx.vm.getAccount(db, accountID)
|
|
|
|
if err != nil {
|
2020-06-08 15:57:05 -07:00
|
|
|
return nil, nil, nil, nil, permError{errDBAccount}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
2020-06-08 15:57:05 -07:00
|
|
|
// 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.)
|
2020-06-17 22:45:58 -07:00
|
|
|
newAccount, err := account.Remove(0, tx.Nonce) // Remove also removes the fee
|
2020-03-10 12:20:34 -07:00
|
|
|
if err != nil {
|
2020-06-08 15:57:05 -07:00
|
|
|
return nil, nil, nil, nil, permError{err}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
currentEvents, err := tx.vm.getCurrentValidators(db, DefaultSubnetID)
|
|
|
|
if err != nil {
|
2020-06-08 15:57:05 -07:00
|
|
|
return nil, nil, nil, nil, permError{fmt.Errorf("couldn't get current validators of default subnet: %v", err)}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
if dsValidator, err := currentEvents.getDefaultSubnetStaker(tx.NodeID); err == nil {
|
|
|
|
if !tx.DurationValidator.BoundedBy(dsValidator.StartTime(), dsValidator.EndTime()) {
|
2020-06-08 15:57:05 -07:00
|
|
|
return nil, nil, nil, nil, permError{errDSValidatorSubset}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
} 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 {
|
2020-06-08 15:57:05 -07:00
|
|
|
return nil, nil, nil, nil, permError{fmt.Errorf("couldn't get pending validators of default subnet: %v", err)}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
dsValidator, err := pendingDSValidators.getDefaultSubnetStaker(tx.NodeID)
|
|
|
|
if err != nil {
|
2020-06-08 15:57:05 -07:00
|
|
|
return nil, nil, nil, nil, permError{errDSValidatorSubset}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
if !tx.DurationValidator.BoundedBy(dsValidator.StartTime(), dsValidator.EndTime()) {
|
2020-06-08 15:57:05 -07:00
|
|
|
return nil, nil, nil, nil, permError{errDSValidatorSubset}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pendingEvents, err := tx.vm.getPendingValidators(db, DefaultSubnetID)
|
|
|
|
if err != nil {
|
2020-06-08 15:57:05 -07:00
|
|
|
return nil, nil, nil, nil, permError{err}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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, DefaultSubnetID); err != nil {
|
2020-06-08 15:57:05 -07:00
|
|
|
return nil, nil, nil, nil, permError{err}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
if err := tx.vm.putAccount(onCommitDB, newAccount); err != nil {
|
2020-06-08 15:57:05 -07:00
|
|
|
return nil, nil, nil, nil, permError{err}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 *addDefaultSubnetDelegatorTx) InitiallyPrefersCommit() bool {
|
|
|
|
return tx.StartTime().After(tx.vm.clock.Time())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (vm *VM) newAddDefaultSubnetDelegatorTx(
|
|
|
|
nonce,
|
|
|
|
weight,
|
|
|
|
startTime,
|
|
|
|
endTime uint64,
|
|
|
|
nodeID ids.ShortID,
|
|
|
|
destination ids.ShortID,
|
|
|
|
networkID uint32,
|
|
|
|
key *crypto.PrivateKeySECP256K1R,
|
|
|
|
) (*addDefaultSubnetDelegatorTx, error) {
|
|
|
|
tx := &addDefaultSubnetDelegatorTx{
|
|
|
|
UnsignedAddDefaultSubnetDelegatorTx: UnsignedAddDefaultSubnetDelegatorTx{
|
|
|
|
DurationValidator: DurationValidator{
|
|
|
|
Validator: Validator{
|
|
|
|
NodeID: nodeID,
|
|
|
|
Wght: weight,
|
|
|
|
},
|
|
|
|
Start: startTime,
|
|
|
|
End: endTime,
|
|
|
|
},
|
|
|
|
NetworkID: networkID,
|
|
|
|
Nonce: nonce,
|
|
|
|
Destination: destination,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
unsignedIntf := interface{}(&tx.UnsignedAddDefaultSubnetDelegatorTx)
|
|
|
|
unsignedBytes, err := Codec.Marshal(&unsignedIntf) // byte repr. of unsigned tx
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
sig, err := key.Sign(unsignedBytes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
copy(tx.Sig[:], sig)
|
|
|
|
|
|
|
|
return tx, tx.initialize(vm)
|
|
|
|
}
|