Added switch for temporary and permanent errors in proposal blocks

This commit is contained in:
StephenButtolph 2020-06-08 18:57:05 -04:00
parent f8c202e4be
commit 8ed43f464c
13 changed files with 191 additions and 155 deletions

View File

@ -24,7 +24,9 @@ func (a *Abort) Verify() error {
// Abort is a decision, so its parent must be a proposal
if !ok {
if err := a.Reject(); err == nil {
a.vm.DB.Commit()
if err := a.vm.DB.Commit(); err != nil {
a.vm.Ctx.Log.Error("error committing Abort block as rejected: %s", err)
}
} else {
a.vm.DB.Abort()
}

View File

@ -56,41 +56,41 @@ func (tx *addDefaultSubnetDelegatorTx) ID() ids.ID { return tx.id }
// SyntacticVerify return nil iff [tx] is valid
// If [tx] is valid, sets [tx.accountID]
func (tx *addDefaultSubnetDelegatorTx) SyntacticVerify() error {
func (tx *addDefaultSubnetDelegatorTx) SyntacticVerify() TxError {
switch {
case tx == nil:
return errNilTx
return tempError{errNilTx}
case !tx.senderID.IsZero():
return nil // Only verify the transaction once
case tx.id.IsZero():
return errInvalidID
return tempError{errInvalidID}
case tx.NetworkID != tx.vm.Ctx.NetworkID:
return errWrongNetworkID
return permError{errWrongNetworkID}
case tx.NodeID.IsZero():
return errInvalidID
return tempError{errInvalidID}
case tx.Wght < MinimumStakeAmount: // Ensure validator is staking at least the minimum amount
return errWeightTooSmall
return permError{errWeightTooSmall}
}
// Ensure staking length is not too short or long
stakingDuration := tx.Duration()
if stakingDuration < MinimumStakingDuration {
return errStakeTooShort
return permError{errStakeTooShort}
} else if stakingDuration > MaximumStakingDuration {
return errStakeTooLong
return permError{errStakeTooLong}
}
unsignedIntf := interface{}(&tx.UnsignedAddDefaultSubnetDelegatorTx)
// Byte representation of the unsigned transaction
unsignedBytes, err := Codec.Marshal(&unsignedIntf)
if err != nil {
return err
return permError{err}
}
// get account to pay tx fee from
key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:])
if err != nil {
return err
return permError{err}
}
tx.senderID = key.Address()
@ -98,7 +98,7 @@ func (tx *addDefaultSubnetDelegatorTx) SyntacticVerify() error {
}
// SemanticVerify this transaction is valid.
func (tx *addDefaultSubnetDelegatorTx) SemanticVerify(db database.Database) (*versiondb.Database, *versiondb.Database, func(), func(), error) {
func (tx *addDefaultSubnetDelegatorTx) SemanticVerify(db database.Database) (*versiondb.Database, *versiondb.Database, func(), func(), TxError) {
if err := tx.SyntacticVerify(); err != nil {
return nil, nil, nil, nil, err
}
@ -106,60 +106,62 @@ func (tx *addDefaultSubnetDelegatorTx) SemanticVerify(db database.Database) (*ve
// Ensure the proposed validator starts after the current timestamp
currentTimestamp, err := tx.vm.getTimestamp(db)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{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)",
return nil, nil, nil, nil, permError{fmt.Errorf("chain timestamp (%s) not before validator's start time (%s)",
currentTimestamp,
validatorStartTime)
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
// 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
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.)
// 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
return nil, nil, nil, nil, permError{err}
}
// 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 {
return nil, nil, nil, nil, fmt.Errorf("couldn't get current validators of default subnet: %v", err)
return nil, nil, nil, nil, permError{fmt.Errorf("couldn't get current validators of default subnet: %v", err)}
}
if dsValidator, err := currentEvents.getDefaultSubnetStaker(tx.NodeID); err == nil {
if !tx.DurationValidator.BoundedBy(dsValidator.StartTime(), dsValidator.EndTime()) {
return nil, nil, nil, nil, errDSValidatorSubset
return nil, nil, nil, nil, permError{errDSValidatorSubset}
}
} 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)
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, errDSValidatorSubset
return nil, nil, nil, nil, permError{errDSValidatorSubset}
}
if !tx.DurationValidator.BoundedBy(dsValidator.StartTime(), dsValidator.EndTime()) {
return nil, nil, nil, nil, errDSValidatorSubset
return nil, nil, nil, nil, permError{errDSValidatorSubset}
}
}
pendingEvents, err := tx.vm.getPendingValidators(db, DefaultSubnetID)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
pendingEvents.Add(tx) // add validator to set of pending validators
@ -168,10 +170,10 @@ func (tx *addDefaultSubnetDelegatorTx) SemanticVerify(db database.Database) (*ve
// update the validator's account by removing the staked $AVA
onCommitDB := versiondb.New(db)
if err := tx.vm.putPendingValidators(onCommitDB, pendingEvents, DefaultSubnetID); err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
if err := tx.vm.putAccount(onCommitDB, newAccount); err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
// If this proposal is aborted, chain state doesn't change

View File

@ -63,44 +63,44 @@ func (tx *addDefaultSubnetValidatorTx) ID() ids.ID { return tx.id }
// SyntacticVerify that this transaction is well formed
// If [tx] is valid, this method also populates [tx.accountID]
func (tx *addDefaultSubnetValidatorTx) SyntacticVerify() error {
func (tx *addDefaultSubnetValidatorTx) SyntacticVerify() TxError {
switch {
case tx == nil:
return errNilTx
return tempError{errNilTx}
case !tx.senderID.IsZero():
return nil // Only verify the transaction once
case tx.id.IsZero():
return errInvalidID
return tempError{errInvalidID}
case tx.NetworkID != tx.vm.Ctx.NetworkID:
return errWrongNetworkID
return permError{errWrongNetworkID}
case tx.NodeID.IsZero():
return errInvalidID
return tempError{errInvalidID}
case tx.Destination.IsZero():
return errInvalidID
return tempError{errInvalidID}
case tx.Wght < MinimumStakeAmount: // Ensure validator is staking at least the minimum amount
return errWeightTooSmall
return permError{errWeightTooSmall}
case tx.Shares > NumberOfShares: // Ensure delegators shares are in the allowed amount
return errTooManyShares
return permError{errTooManyShares}
}
// Ensure staking length is not too short or long
stakingDuration := tx.Duration()
if stakingDuration < MinimumStakingDuration {
return errStakeTooShort
return permError{errStakeTooShort}
} else if stakingDuration > MaximumStakingDuration {
return errStakeTooLong
return permError{errStakeTooLong}
}
// Byte representation of the unsigned transaction
unsignedIntf := interface{}(&tx.UnsignedAddDefaultSubnetValidatorTx)
unsignedBytes, err := Codec.Marshal(&unsignedIntf)
if err != nil {
return err
return permError{err}
}
key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:]) // the public key that signed [tx]
if err != nil {
return err
return permError{err}
}
tx.senderID = key.Address()
@ -108,7 +108,7 @@ func (tx *addDefaultSubnetValidatorTx) SyntacticVerify() error {
}
// SemanticVerify this transaction is valid.
func (tx *addDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (*versiondb.Database, *versiondb.Database, func(), func(), error) {
func (tx *addDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (*versiondb.Database, *versiondb.Database, func(), func(), TxError) {
if err := tx.SyntacticVerify(); err != nil {
return nil, nil, nil, nil, err
}
@ -116,13 +116,13 @@ func (tx *addDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (*ve
// Ensure the proposed validator starts after the current time
currentTime, err := tx.vm.getTimestamp(db)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
startTime := tx.StartTime()
if !currentTime.Before(startTime) {
return nil, nil, nil, nil, fmt.Errorf("chain timestamp (%s) not before validator's start time (%s)",
return nil, nil, nil, nil, permError{fmt.Errorf("chain timestamp (%s) not before validator's start time (%s)",
currentTime,
startTime)
startTime)}
}
// Get the account that is paying the transaction fee and, if the proposal is to add a validator
@ -131,7 +131,7 @@ func (tx *addDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (*ve
accountID := tx.senderID
account, err := tx.vm.getAccount(db, accountID)
if err != nil {
return nil, nil, nil, nil, errDBAccount
return nil, nil, nil, nil, permError{errDBAccount}
}
// If the transaction adds a validator to the default subnet, also deduct
@ -142,31 +142,31 @@ func (tx *addDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (*ve
// to the pending validator set. (Increase the account's nonce; decrease its balance.)
newAccount, err := account.Remove(amount, tx.Nonce)
if err != nil {
return nil, nil, nil, nil, err
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, DefaultSubnetID)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{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 default validator set",
tx.NodeID)
return nil, nil, nil, nil, permError{fmt.Errorf("validator with ID %s already in the current default validator set",
tx.NodeID)}
}
// Ensure the proposed validator is not already slated to validate for the specified subnet
pendingEvents, err := tx.vm.getPendingValidators(db, DefaultSubnetID)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{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 default validator set",
tx.NodeID)
return nil, nil, nil, nil, permError{fmt.Errorf("validator with ID %s already in the pending default validator set",
tx.NodeID)}
}
pendingEvents.Add(tx) // add validator to set of pending validators
@ -175,10 +175,10 @@ func (tx *addDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (*ve
// update the validator's account by removing the staked $AVA
onCommitDB := versiondb.New(db)
if err := tx.vm.putPendingValidators(onCommitDB, pendingEvents, DefaultSubnetID); err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
if err := tx.vm.putAccount(onCommitDB, newAccount); err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
// If this proposal is aborted, chain state doesn't change

View File

@ -77,39 +77,39 @@ 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 {
func (tx *addNonDefaultSubnetValidatorTx) SyntacticVerify() TxError {
switch {
case tx == nil:
return errNilTx
return tempError{errNilTx}
case !tx.senderID.IsZero():
return nil // Only verify the transaction once
case tx.id.IsZero():
return errInvalidID
return tempError{errInvalidID}
case tx.NetworkID != tx.vm.Ctx.NetworkID:
return errWrongNetworkID
return permError{errWrongNetworkID}
case tx.NodeID.IsZero():
return errInvalidID
return tempError{errInvalidID}
case tx.Subnet.IsZero():
return errInvalidID
return tempError{errInvalidID}
case tx.Wght == 0: // Ensure the validator has some weight
return errWeightTooSmall
return permError{errWeightTooSmall}
case !crypto.IsSortedAndUniqueSECP2561RSigs(tx.ControlSigs):
return errSigsNotSorted
return permError{errSigsNotSorted}
}
// Ensure staking length is not too short or long
stakingDuration := tx.Duration()
if stakingDuration < MinimumStakingDuration {
return errStakeTooShort
return permError{errStakeTooShort}
} else if stakingDuration > MaximumStakingDuration {
return errStakeTooLong
return permError{errStakeTooLong}
}
// Byte representation of the unsigned transaction
unsignedIntf := interface{}(&tx.UnsignedAddNonDefaultSubnetValidatorTx)
unsignedBytes, err := Codec.Marshal(&unsignedIntf)
if err != nil {
return err
return permError{err}
}
unsignedBytesHash := hashing.ComputeHash256(unsignedBytes)
@ -118,7 +118,7 @@ func (tx *addNonDefaultSubnetValidatorTx) SyntacticVerify() error {
for i, sig := range tx.ControlSigs {
key, err := tx.vm.factory.RecoverHashPublicKey(unsignedBytesHash, sig[:])
if err != nil {
return err
return permError{err}
}
tx.controlIDs[i] = key.Address()
}
@ -126,7 +126,7 @@ func (tx *addNonDefaultSubnetValidatorTx) SyntacticVerify() error {
// get account to pay tx fee from
key, err := tx.vm.factory.RecoverHashPublicKey(unsignedBytesHash, tx.PayerSig[:])
if err != nil {
return err
return permError{err}
}
tx.senderID = key.Address()
@ -149,7 +149,7 @@ func (h *EventHeap) getDefaultSubnetStaker(id ids.ShortID) (*addDefaultSubnetVal
}
// SemanticVerify this transaction is valid.
func (tx *addNonDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (*versiondb.Database, *versiondb.Database, func(), func(), error) {
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
@ -158,7 +158,7 @@ func (tx *addNonDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (
// 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
return nil, nil, nil, nil, permError{err}
}
var subnet *CreateSubnetTx
for _, sn := range subnets {
@ -168,22 +168,22 @@ func (tx *addNonDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (
}
}
if subnet == nil {
return nil, nil, nil, nil, fmt.Errorf("there is no subnet with ID %s", tx.SubnetID())
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, fmt.Errorf("expected tx to have %d control sigs but has %d", subnet.Threshold, len(tx.ControlSigs))
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, errors.New("control signatures aren't sorted")
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, errors.New("tx has control signature from key not in subnet's ControlKeys")
return nil, nil, nil, nil, permError{errors.New("tx has control signature from key not in subnet's ControlKeys")}
}
}
@ -191,46 +191,46 @@ func (tx *addNonDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (
// 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)
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,
fmt.Errorf("time validating subnet [%v, %v] not subset of time validating default subnet [%v, %v]",
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())
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)
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,
fmt.Errorf("validator would not be validating default subnet while validating non-default subnet")
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,
fmt.Errorf("time validating subnet [%v, %v] not subset of time validating default subnet [%v, %v]",
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())
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)
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, fmt.Errorf("chain timestamp (%s) not before validator's start time (%s)",
return nil, nil, nil, nil, permError{fmt.Errorf("chain timestamp (%s) not before validator's start time (%s)",
currentTimestamp,
validatorStartTime)
validatorStartTime)}
}
// Get the account that is paying the transaction fee and, if the proposal is to add a validator
@ -239,42 +239,40 @@ func (tx *addNonDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (
accountID := tx.senderID
account, err := tx.vm.getAccount(db, accountID)
if err != nil {
return nil, nil, nil, nil, errDBAccount
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, err
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, fmt.Errorf("couldn't get current validators of subnet %s: %v", tx.Subnet, err)
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, fmt.Errorf("validator with ID %s already in the current validator set for subnet with ID %s",
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,
)
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)
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, fmt.Errorf("validator with ID %s already in the pending validator set for subnet with ID %s",
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,
)
tx.Subnet)}
}
pendingEvents.Add(tx) // add validator to set of pending validators
@ -283,10 +281,10 @@ func (tx *addNonDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (
// 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)
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, fmt.Errorf("couldn't put account: %v", err)
return nil, nil, nil, nil, permError{fmt.Errorf("couldn't put account: %v", err)}
}
// If this proposal is aborted, chain state doesn't change

View File

@ -4,6 +4,7 @@
package platformvm
import (
"bytes"
"fmt"
"time"
@ -35,47 +36,47 @@ func (tx *advanceTimeTx) initialize(vm *VM) error {
func (tx *advanceTimeTx) Timestamp() time.Time { return time.Unix(int64(tx.Time), 0) }
// SyntacticVerify that this transaction is well formed
func (tx *advanceTimeTx) SyntacticVerify() error {
func (tx *advanceTimeTx) SyntacticVerify() TxError {
switch {
case tx == nil:
return errNilTx
return tempError{errNilTx}
case tx.vm.clock.Time().Add(Delta).Before(tx.Timestamp()):
return errTimeTooAdvanced
return tempError{errTimeTooAdvanced}
default:
return nil
}
}
// SemanticVerify this transaction is valid.
func (tx *advanceTimeTx) SemanticVerify(db database.Database) (*versiondb.Database, *versiondb.Database, func(), func(), error) {
func (tx *advanceTimeTx) SemanticVerify(db database.Database) (*versiondb.Database, *versiondb.Database, func(), func(), TxError) {
if err := tx.SyntacticVerify(); err != nil {
return nil, nil, nil, nil, err
}
currentTimestamp, err := tx.vm.getTimestamp(db)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
if tx.Time <= uint64(currentTimestamp.Unix()) {
return nil, nil, nil, nil, fmt.Errorf("proposed timestamp %s not after current timestamp %s",
return nil, nil, nil, nil, permError{fmt.Errorf("proposed timestamp %s not after current timestamp %s",
tx.Timestamp(),
currentTimestamp)
currentTimestamp)}
}
// Only allow timestamp to move forward as far as the next validator's end time
nextValidatorEndTime := tx.vm.nextValidatorChangeTime(db, false)
if tx.Time > uint64(nextValidatorEndTime.Unix()) {
return nil, nil, nil, nil, fmt.Errorf("proposed timestamp %v later than next validator end time %s",
return nil, nil, nil, nil, permError{fmt.Errorf("proposed timestamp %v later than next validator end time %s",
tx.Time,
nextValidatorEndTime)
nextValidatorEndTime)}
}
// Only allow timestamp to move forward as far as the next pending validator's start time
nextValidatorStartTime := tx.vm.nextValidatorChangeTime(db, true)
if tx.Time > uint64(nextValidatorStartTime.Unix()) {
return nil, nil, nil, nil, fmt.Errorf("proposed timestamp %v later than next validator start time %s",
return nil, nil, nil, nil, permError{fmt.Errorf("proposed timestamp %v later than next validator start time %s",
tx.Time,
nextValidatorStartTime)
nextValidatorStartTime)}
}
// Calculate what the validator sets will be given new timestamp
@ -85,19 +86,19 @@ func (tx *advanceTimeTx) SemanticVerify(db database.Database) (*versiondb.Databa
// Specify what the state of the chain will be if this proposal is committed
onCommitDB := versiondb.New(db)
if err := tx.vm.putTimestamp(onCommitDB, tx.Timestamp()); err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
current, pending, _, _, err := tx.vm.calculateValidators(db, tx.Timestamp(), DefaultSubnetID)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
if err := tx.vm.putCurrentValidators(onCommitDB, current, DefaultSubnetID); err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
if err := tx.vm.putPendingValidators(onCommitDB, pending, DefaultSubnetID); err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
// For each Subnet, calculate what current and pending validator sets should be
@ -106,23 +107,23 @@ func (tx *advanceTimeTx) SemanticVerify(db database.Database) (*versiondb.Databa
// Key: Subnet ID
// Value: IDs of validators that will have started validating this Subnet when
// timestamp is advanced to tx.Timestamp()
startedValidating := make(map[ids.ID]ids.ShortSet, 0)
startedValidating := make(map[[32]byte]ids.ShortSet, 0)
subnets, err := tx.vm.getSubnets(db)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
for _, subnet := range subnets {
current, pending, started, _, err := tx.vm.calculateValidators(db, tx.Timestamp(), subnet.id)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
if err := tx.vm.putCurrentValidators(onCommitDB, current, subnet.id); err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
if err := tx.vm.putPendingValidators(onCommitDB, pending, subnet.id); err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
startedValidating[subnet.ID()] = started
startedValidating[subnet.ID().Key()] = started
}
// If this block is committed, update the validator sets
@ -154,7 +155,7 @@ func (tx *advanceTimeTx) SemanticVerify(db database.Database) (*versiondb.Databa
continue
}
for _, chain := range chains {
if chain.SubnetID.Equals(subnetID) {
if bytes.Equal(subnetID[:], chain.SubnetID.Bytes()) {
tx.vm.createChain(chain)
}
}

View File

@ -24,7 +24,9 @@ func (c *Commit) Verify() error {
parent, ok := c.parentBlock().(*ProposalBlock)
if !ok {
if err := c.Reject(); err == nil {
c.vm.DB.Commit()
if err := c.vm.DB.Commit(); err != nil {
c.vm.Ctx.Log.Error("error committing Commit block as rejected: %s", err)
}
} else {
c.vm.DB.Abort()
}

19
vms/platformvm/error.go Normal file
View File

@ -0,0 +1,19 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package platformvm
// TxError provides the ability for errors to be distinguished as permenant or
// temporary
type TxError interface {
error
Temporary() bool
}
type tempError struct{ error }
func (tempError) Temporary() bool { return true }
type permError struct{ error }
func (permError) Temporary() bool { return false }

View File

@ -16,7 +16,13 @@ import (
type ProposalTx interface {
initialize(vm *VM) error
// Attempts to verify this transaction with the provided state.
SemanticVerify(database.Database) (onCommitDB *versiondb.Database, onAbortDB *versiondb.Database, onCommitFunc func(), onAbortFunc func(), err error)
SemanticVerify(database.Database) (
onCommitDB *versiondb.Database,
onAbortDB *versiondb.Database,
onCommitFunc func(),
onAbortFunc func(),
err TxError,
)
InitiallyPrefersCommit() bool
}
@ -99,7 +105,9 @@ func (pb *ProposalBlock) Verify() error {
parent, ok := parentIntf.(decision)
if !ok {
if err := pb.Reject(); err == nil {
pb.vm.DB.Commit()
if err := pb.vm.DB.Commit(); err != nil {
pb.vm.Ctx.Log.Error("error committing Proposal block as rejected: %s", err)
}
} else {
pb.vm.DB.Abort()
}
@ -109,19 +117,16 @@ func (pb *ProposalBlock) Verify() error {
// pdb is the database if this block's parent is accepted
pdb := parent.onAccept()
isAdvanceTimeProposal := false
switch pb.Tx.(type) {
case *advanceTimeTx:
isAdvanceTimeProposal = true
}
var err error
var err TxError
pb.onCommitDB, pb.onAbortDB, pb.onCommitFunc, pb.onAbortFunc, err = pb.Tx.SemanticVerify(pdb)
if err != nil {
// If this block's transaction proposes to advance the timestamp, the transaction may fail
// verification now but be valid in the future, so don't (permanently) mark the block as rejected.
if !isAdvanceTimeProposal {
if !err.Temporary() {
if err := pb.Reject(); err == nil {
pb.vm.DB.Commit()
if err := pb.vm.DB.Commit(); err != nil {
pb.vm.Ctx.Log.Error("error committing Proposal block as rejected: %s", err)
}
} else {
pb.vm.DB.Abort()
}

View File

@ -41,12 +41,12 @@ func (tx *rewardValidatorTx) initialize(vm *VM) error {
}
// SyntacticVerify that this transaction is well formed
func (tx *rewardValidatorTx) SyntacticVerify() error {
func (tx *rewardValidatorTx) SyntacticVerify() TxError {
switch {
case tx == nil:
return errNilTx
return tempError{errNilTx}
case tx.TxID.IsZero():
return errInvalidID
return tempError{errInvalidID}
default:
return nil
}
@ -58,39 +58,39 @@ func (tx *rewardValidatorTx) SyntacticVerify() error {
// The next validator to be removed must be the validator specified in this block.
// The next validator to be removed must be have an end time equal to the current
// chain timestamp.
func (tx *rewardValidatorTx) SemanticVerify(db database.Database) (*versiondb.Database, *versiondb.Database, func(), func(), error) {
func (tx *rewardValidatorTx) SemanticVerify(db database.Database) (*versiondb.Database, *versiondb.Database, func(), func(), TxError) {
if err := tx.SyntacticVerify(); err != nil {
return nil, nil, nil, nil, err
}
if db == nil {
return nil, nil, nil, nil, errDBNil
return nil, nil, nil, nil, tempError{errDBNil}
}
currentEvents, err := tx.vm.getCurrentValidators(db, DefaultSubnetID)
if err != nil {
return nil, nil, nil, nil, errDBCurrentValidators
return nil, nil, nil, nil, permError{errDBCurrentValidators}
}
if currentEvents.Len() == 0 { // there is no validator to remove
return nil, nil, nil, nil, errEmptyValidatingSet
return nil, nil, nil, nil, permError{errEmptyValidatingSet}
}
vdrTx := currentEvents.Peek()
if txID := vdrTx.ID(); !txID.Equals(tx.TxID) {
return nil, nil, nil, nil, fmt.Errorf("attempting to remove TxID: %s. Should be removing %s",
return nil, nil, nil, nil, permError{fmt.Errorf("attempting to remove TxID: %s. Should be removing %s",
tx.TxID,
txID)
txID)}
}
// Verify that the chain's timestamp is the validator's end time
currentTime, err := tx.vm.getTimestamp(db)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
if endTime := vdrTx.EndTime(); !endTime.Equal(currentTime) {
return nil, nil, nil, nil, fmt.Errorf("attempting to remove TxID: %s before their end time %s",
return nil, nil, nil, nil, permError{fmt.Errorf("attempting to remove TxID: %s before their end time %s",
tx.TxID,
endTime)
endTime)}
}
heap.Pop(currentEvents) // Remove validator from the validator set
@ -99,14 +99,14 @@ func (tx *rewardValidatorTx) SemanticVerify(db database.Database) (*versiondb.Da
// If this tx's proposal is committed, remove the validator from the validator set and update the
// account balance to reflect the return of staked $AVA and their reward.
if err := tx.vm.putCurrentValidators(onCommitDB, currentEvents, DefaultSubnetID); err != nil {
return nil, nil, nil, nil, errDBPutCurrentValidators
return nil, nil, nil, nil, permError{errDBPutCurrentValidators}
}
onAbortDB := versiondb.New(db)
// If this tx's proposal is aborted, remove the validator from the validator set and update the
// account balance to reflect the return of staked $AVA. The validator receives no reward.
if err := tx.vm.putCurrentValidators(onAbortDB, currentEvents, DefaultSubnetID); err != nil {
return nil, nil, nil, nil, errDBPutCurrentValidators
return nil, nil, nil, nil, permError{errDBPutCurrentValidators}
}
switch vdrTx := vdrTx.(type) {
@ -144,15 +144,15 @@ func (tx *rewardValidatorTx) SemanticVerify(db database.Database) (*versiondb.Da
}
if err := tx.vm.putAccount(onCommitDB, accountWithReward); err != nil {
return nil, nil, nil, nil, errDBPutAccount
return nil, nil, nil, nil, tempError{errDBPutAccount}
}
if err := tx.vm.putAccount(onAbortDB, accountNoReward); err != nil {
return nil, nil, nil, nil, errDBPutAccount
return nil, nil, nil, nil, tempError{errDBPutAccount}
}
case *addDefaultSubnetDelegatorTx:
parentTx, err := currentEvents.getDefaultSubnetStaker(vdrTx.NodeID)
if err != nil {
return nil, nil, nil, nil, err
return nil, nil, nil, nil, permError{err}
}
duration := vdrTx.Duration()
@ -201,10 +201,10 @@ func (tx *rewardValidatorTx) SemanticVerify(db database.Database) (*versiondb.Da
}
if err := tx.vm.putAccount(onCommitDB, delegatorAccountWithReward); err != nil {
return nil, nil, nil, nil, errDBPutAccount
return nil, nil, nil, nil, tempError{errDBPutAccount}
}
if err := tx.vm.putAccount(onAbortDB, delegatorAccountNoReward); err != nil {
return nil, nil, nil, nil, errDBPutAccount
return nil, nil, nil, nil, tempError{errDBPutAccount}
}
validatorAccountID := parentTx.Destination
@ -225,10 +225,10 @@ func (tx *rewardValidatorTx) SemanticVerify(db database.Database) (*versiondb.Da
}
if err := tx.vm.putAccount(onCommitDB, validatorAccountWithReward); err != nil {
return nil, nil, nil, nil, errDBPutAccount
return nil, nil, nil, nil, permError{errDBPutAccount}
}
default:
return nil, nil, nil, nil, errShouldBeDSValidator
return nil, nil, nil, nil, permError{errShouldBeDSValidator}
}
// Regardless of whether this tx is committed or aborted, update the

View File

@ -390,6 +390,7 @@ func (service *Service) SampleValidators(_ *http.Request, args *SampleValidators
*************** Get/Create Accounts ******************
******************************************************
*/
// GetAccountArgs are the arguments for calling GetAccount
type GetAccountArgs struct {
// Address of the account we want the information about

View File

@ -55,7 +55,9 @@ func (sb *StandardBlock) Verify() error {
parent, ok := parentBlock.(decision)
if !ok {
if err := sb.Reject(); err == nil {
sb.vm.DB.Commit()
if err := sb.vm.DB.Commit(); err != nil {
sb.vm.Ctx.Log.Error("error committing Standard block as rejected: %s", err)
}
} else {
sb.vm.DB.Abort()
}
@ -70,7 +72,9 @@ func (sb *StandardBlock) Verify() error {
onAccept, err := tx.SemanticVerify(sb.onAcceptDB)
if err != nil {
if err := sb.Reject(); err == nil {
sb.vm.DB.Commit()
if err := sb.vm.DB.Commit(); err != nil {
sb.vm.Ctx.Log.Error("error committing Standard block as rejected: %s", err)
}
} else {
sb.vm.DB.Abort()
}

View File

@ -36,6 +36,7 @@ type APIAccount struct {
Balance json.Uint64 `json:"balance"`
}
// FormattedAPIAccount is an APIAccount but allows for a formatted Address
type FormattedAPIAccount struct {
Address string `json:"address"`
Nonce json.Uint64 `json:"nonce"`
@ -76,6 +77,7 @@ type APIDefaultSubnetValidator struct {
DelegationFeeRate json.Uint32 `json:"delegationFeeRate"`
}
// FormattedAPIValidator allows for a formatted address
type FormattedAPIValidator struct {
StartTime json.Uint64 `json:"startTime"`
EndTime json.Uint64 `json:"endTime"`

View File

@ -867,7 +867,7 @@ func (vm *VM) GetAtomicUTXOs(addrs ids.Set) ([]*ava.UTXO, error) {
return utxos, nil
}
// ParseAddr ...
// ParseAddress ...
func (vm *VM) ParseAddress(addrStr string) (ids.ShortID, error) {
cb58 := formatting.CB58{}
err := cb58.FromString(addrStr)
@ -877,8 +877,8 @@ func (vm *VM) ParseAddress(addrStr string) (ids.ShortID, error) {
return ids.ToShortID(cb58.Bytes)
}
// Assumes addrID is not empty
// FormatAddress ...
// Assumes addrID is not empty
func (vm *VM) FormatAddress(addrID ids.ShortID) string {
return addrID.String()
}