Skip to content

Commit

Permalink
refactor(state): define errors for state package (#1457)
Browse files Browse the repository at this point in the history
  • Loading branch information
b00f authored Sep 24, 2024
1 parent 459c7d4 commit d776f0c
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 108 deletions.
65 changes: 58 additions & 7 deletions state/errors.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
package state

import (
"errors"
"fmt"

"github.com/pactus-project/pactus/types/certificate"
"github.com/pactus-project/pactus/crypto"
"github.com/pactus-project/pactus/crypto/hash"
"github.com/pactus-project/pactus/types/amount"
"github.com/pactus-project/pactus/types/vote"
)

// ErrInvalidBlockVersion indicates that the block version is not valid.
var ErrInvalidBlockVersion = errors.New("invalid block version")

// ErrInvalidSubsidyTransaction indicates that the subsidy transaction is not valid.
var ErrInvalidSubsidyTransaction = errors.New("invalid subsidy transaction")

// ErrDuplicatedSubsidyTransaction indicates that there is more than one subsidy transaction
// inside the block.
var ErrDuplicatedSubsidyTransaction = errors.New("duplicated subsidy transaction")

// ErrInvalidSortitionSeed indicates that the block's sortition seed is either invalid or unverifiable.
var ErrInvalidSortitionSeed = errors.New("invalid sortition seed")

// ErrInvalidCertificate indicates that the block certificate is invalid.
var ErrInvalidCertificate = errors.New("invalid certificate")

// InvalidSubsidyAmountError is returned when the amount of the subsidy transaction is not as expected.
type InvalidSubsidyAmountError struct {
Expected amount.Amount
Got amount.Amount
}

func (e InvalidSubsidyAmountError) Error() string {
return fmt.Sprintf("invalid subsidy amount, expected: %v, got: %v", e.Expected, e.Got)
}

// InvalidVoteForCertificateError is returned when an attempt to update
// the last certificate with an invalid vote is made.
type InvalidVoteForCertificateError struct {
Expand All @@ -18,12 +47,34 @@ func (e InvalidVoteForCertificateError) Error() string {
e.Vote.String())
}

// InvalidBlockCertificateError is returned when the given certificate is invalid.
type InvalidBlockCertificateError struct {
Cert *certificate.BlockCertificate
// InvalidStateRootHashError is returned when the state root hash of the block
// does not match the current state root hash.
type InvalidStateRootHashError struct {
Expected hash.Hash
Got hash.Hash
}

func (e InvalidStateRootHashError) Error() string {
return fmt.Sprintf("invalid state root hash, expected: %s, got: %s",
e.Expected, e.Got)
}

// InvalidProposerError is returned when the block proposer is not as expected.
type InvalidProposerError struct {
Expected crypto.Address
Got crypto.Address
}

func (e InvalidProposerError) Error() string {
return fmt.Sprintf("invalid block proposer, expected: %s, got: %s",
e.Expected, e.Got)
}

// InvalidBlockTimeError is returned when the block time is not valid.
type InvalidBlockTimeError struct {
Reason string
}

func (e InvalidBlockCertificateError) Error() string {
return fmt.Sprintf("invalid certificate for block %d",
e.Cert.Height())
func (e InvalidBlockTimeError) Error() string {
return fmt.Sprintf("invalid block time: %s", e.Reason)
}
21 changes: 10 additions & 11 deletions state/execution.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package state

import (
"fmt"

"github.com/pactus-project/pactus/crypto"
"github.com/pactus-project/pactus/execution"
"github.com/pactus-project/pactus/sandbox"
"github.com/pactus-project/pactus/types/block"
"github.com/pactus-project/pactus/types/tx"
"github.com/pactus-project/pactus/util/errors"
)

func (st *state) executeBlock(b *block.Block, sb sandbox.Sandbox, check bool) error {
Expand All @@ -16,13 +17,11 @@ func (st *state) executeBlock(b *block.Block, sb sandbox.Sandbox, check bool) er
isSubsidyTx := (i == 0)
if isSubsidyTx {
if !trx.IsSubsidyTx() {
return errors.Errorf(errors.ErrInvalidTx,
"first transaction should be a subsidy transaction")
return ErrInvalidSubsidyTransaction
}
subsidyTrx = trx
} else if trx.IsSubsidyTx() {
return errors.Errorf(errors.ErrInvalidTx,
"duplicated subsidy transaction")
return ErrDuplicatedSubsidyTransaction
}

if check {
Expand All @@ -45,8 +44,10 @@ func (st *state) executeBlock(b *block.Block, sb sandbox.Sandbox, check bool) er
accumulatedFee := sb.AccumulatedFee()
subsidyAmt := st.params.BlockReward + sb.AccumulatedFee()
if subsidyTrx.Payload().Value() != subsidyAmt {
return errors.Errorf(errors.ErrInvalidTx,
"invalid subsidy amount, expected %v, got %v", subsidyAmt, subsidyTrx.Payload().Value())
return InvalidSubsidyAmountError{
Expected: subsidyAmt,
Got: subsidyTrx.Payload().Value(),
}
}

// Claim accumulated fees
Expand All @@ -61,14 +62,12 @@ func (st *state) checkEd25519Fork(trx *tx.Tx) error {
// TODO: remove me after enabling Ed255519
if trx.Payload().Signer().Type() == crypto.AddressTypeEd25519Account {
if st.genDoc.ChainType().IsMainnet() {
return errors.Errorf(errors.ErrInvalidTx,
"ed255519 not supported yet")
return fmt.Errorf("ed255519 not supported yet")
}

if st.genDoc.ChainType().IsTestnet() {
if st.lastInfo.BlockHeight() < 1_320_000 {
return errors.Errorf(errors.ErrInvalidTx,
"ed255519 not supported yet")
return fmt.Errorf("ed255519 not supported yet")
}
}
}
Expand Down
30 changes: 19 additions & 11 deletions state/execution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"time"

"github.com/pactus-project/pactus/crypto"
"github.com/pactus-project/pactus/execution/executor"
"github.com/pactus-project/pactus/types/amount"
"github.com/pactus-project/pactus/types/block"
"github.com/pactus-project/pactus/types/tx"
"github.com/pactus-project/pactus/util/testsuite"
Expand Down Expand Up @@ -64,15 +66,19 @@ func TestExecuteBlock(t *testing.T) {
assert.NoError(t, td.state.AddPendingTx(invSubsidyTx))
assert.NoError(t, td.state.AddPendingTx(validTx1))

t.Run("Subsidy tx is invalid", func(t *testing.T) {
t.Run("Subsidy amount is invalid", func(t *testing.T) {
txs := block.NewTxs()
txs.Append(invSubsidyTx)
txs.Append(validTx1)
invBlock := block.MakeBlock(1, time.Now(), txs, td.state.lastInfo.BlockHash(),
td.state.stateRoot(), td.state.lastInfo.Certificate(),
td.state.lastInfo.SortitionSeed(), proposerAddr)
sb := td.state.concreteSandbox()

assert.Error(t, td.state.executeBlock(invBlock, sb, true))
err := td.state.executeBlock(invBlock, sb, true)
assert.ErrorIs(t, err, InvalidSubsidyAmountError{
Expected: amount.Amount(1e9 + 1000),
Got: amount.Amount(1e9 + 1001),
})
})

t.Run("Has invalid tx", func(t *testing.T) {
Expand All @@ -83,8 +89,10 @@ func TestExecuteBlock(t *testing.T) {
td.state.stateRoot(), td.state.lastInfo.Certificate(),
td.state.lastInfo.SortitionSeed(), proposerAddr)
sb := td.state.concreteSandbox()

assert.Error(t, td.state.executeBlock(invBlock, sb, true))
err := td.state.executeBlock(invBlock, sb, true)
assert.ErrorIs(t, err, executor.AccountNotFoundError{
Address: invTransferTx.Payload().Signer(),
})
})

t.Run("Subsidy is not first tx", func(t *testing.T) {
Expand All @@ -95,8 +103,8 @@ func TestExecuteBlock(t *testing.T) {
td.state.stateRoot(), td.state.lastInfo.Certificate(),
td.state.lastInfo.SortitionSeed(), proposerAddr)
sb := td.state.concreteSandbox()

assert.Error(t, td.state.executeBlock(invBlock, sb, true))
err := td.state.executeBlock(invBlock, sb, true)
assert.ErrorIs(t, err, ErrInvalidSubsidyTransaction)
})

t.Run("Has no subsidy", func(t *testing.T) {
Expand All @@ -106,8 +114,8 @@ func TestExecuteBlock(t *testing.T) {
td.state.stateRoot(), td.state.lastInfo.Certificate(),
td.state.lastInfo.SortitionSeed(), proposerAddr)
sb := td.state.concreteSandbox()

assert.Error(t, td.state.executeBlock(invBlock, sb, true))
err := td.state.executeBlock(invBlock, sb, true)
assert.ErrorIs(t, err, ErrInvalidSubsidyTransaction)
})

t.Run("Two subsidy transactions", func(t *testing.T) {
Expand All @@ -118,8 +126,8 @@ func TestExecuteBlock(t *testing.T) {
td.state.stateRoot(), td.state.lastInfo.Certificate(),
td.state.lastInfo.SortitionSeed(), proposerAddr)
sb := td.state.concreteSandbox()

assert.Error(t, td.state.executeBlock(invBlock, sb, true))
err := td.state.executeBlock(invBlock, sb, true)
assert.ErrorIs(t, err, ErrDuplicatedSubsidyTransaction)
})

t.Run("OK", func(t *testing.T) {
Expand Down
30 changes: 19 additions & 11 deletions state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
"github.com/pactus-project/pactus/types/validator"
"github.com/pactus-project/pactus/types/vote"
"github.com/pactus-project/pactus/util"
"github.com/pactus-project/pactus/util/errors"
"github.com/pactus-project/pactus/util/logger"
"github.com/pactus-project/pactus/util/persistentmerkle"
"github.com/pactus-project/pactus/util/simplemerkle"
Expand Down Expand Up @@ -305,9 +304,9 @@ func (st *state) UpdateLastCertificate(v *vote.Vote) error {
return nil
}

func (st *state) createSubsidyTx(rewardAddr crypto.Address, fee amount.Amount) *tx.Tx {
func (st *state) createSubsidyTx(rewardAddr crypto.Address, accumulatedFee amount.Amount) *tx.Tx {
lockTime := st.lastInfo.BlockHeight() + 1
transaction := tx.NewSubsidyTx(lockTime, rewardAddr, st.params.BlockReward+fee)
transaction := tx.NewSubsidyTx(lockTime, rewardAddr, st.params.BlockReward+accumulatedFee)

return transaction
}
Expand Down Expand Up @@ -345,7 +344,7 @@ func (st *state) ProposeBlock(valKey *bls.ValidatorKey, rewardAddr crypto.Addres
// probably the node is shutting down.
st.logger.Error("no subsidy transaction")

return nil, errors.Errorf(errors.ErrInvalidBlock, "no subsidy transaction")
return nil, ErrInvalidSubsidyTransaction
}
txs.Prepend(subsidyTx)
prevSeed := st.lastInfo.SortitionSeed()
Expand Down Expand Up @@ -408,8 +407,6 @@ func (st *state) CommitBlock(blk *block.Block, cert *certificate.BlockCertificat
st.logger.Panic("a possible fork is detected",
"our hash", st.lastInfo.BlockHash(),
"block hash", blk.Header().PrevBlockHash())

return errors.Error(errors.ErrInvalidBlock)
}

err = st.validateBlock(blk, cert.Round())
Expand Down Expand Up @@ -551,19 +548,30 @@ func (st *state) commitSandbox(sb sandbox.Sandbox, round int16) {

func (st *state) validateBlockTime(t time.Time) error {
if t.Second()%st.params.BlockIntervalInSecond != 0 {
return errors.Errorf(errors.ErrInvalidBlock, "block time (%s) is not rounded", t.String())
return InvalidBlockTimeError{
Reason: fmt.Sprintf("block time (%s) is not rounded",
t.String()),
}
}
if t.Before(st.lastInfo.BlockTime()) {
return errors.Errorf(errors.ErrInvalidBlock, "block time (%s) is before the last block time", t.String())
return InvalidBlockTimeError{
Reason: fmt.Sprintf("block time (%s) is before the last block time (%s)",
t.String(), st.lastInfo.BlockTime()),
}
}
if t.Equal(st.lastInfo.BlockTime()) {
return errors.Errorf(errors.ErrInvalidBlock, "block time (%s) is same as the last block time", t.String())
return InvalidBlockTimeError{
Reason: fmt.Sprintf("block time (%s) is same as the last block time",
t.String()),
}
}
proposeTime := st.proposeNextBlockTime()
threshold := st.params.BlockInterval()
if t.After(proposeTime.Add(threshold)) {
return errors.Errorf(errors.ErrInvalidBlock, "block time (%s) is more than threshold (%s)",
t.String(), proposeTime.String())
return InvalidBlockTimeError{
Reason: fmt.Sprintf("block time (%s) is more than threshold (%s)",
t.String(), proposeTime.String()),
}
}

return nil
Expand Down
26 changes: 13 additions & 13 deletions state/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,33 @@ import (
"github.com/pactus-project/pactus/crypto/hash"
"github.com/pactus-project/pactus/types/block"
"github.com/pactus-project/pactus/types/certificate"
"github.com/pactus-project/pactus/util/errors"
)

func (st *state) validateBlock(blk *block.Block, round int16) error {
if blk.Header().Version() != st.params.BlockVersion {
return errors.Errorf(errors.ErrInvalidBlock,
"invalid version")
return ErrInvalidBlockVersion
}

if blk.Header().StateRoot() != st.stateRoot() {
return errors.Errorf(errors.ErrInvalidBlock,
"state root is not same as we expected, expected %v, got %v", st.stateRoot(), blk.Header().StateRoot())
return InvalidStateRootHashError{
Expected: st.stateRoot(),
Got: blk.Header().StateRoot(),
}
}

// Verify proposer
proposer := st.committee.Proposer(round)
if proposer.Address() != blk.Header().ProposerAddress() {
return errors.Errorf(errors.ErrInvalidBlock,
"invalid proposer, expected %s, got %s", proposer.Address(), blk.Header().ProposerAddress())
return InvalidProposerError{
Expected: proposer.Address(),
Got: blk.Header().ProposerAddress(),
}
}

// Validate sortition seed
seed := blk.Header().SortitionSeed()
if !seed.Verify(proposer.PublicKey(), st.lastInfo.SortitionSeed()) {
return errors.Errorf(errors.ErrInvalidBlock, "invalid sortition seed")
return ErrInvalidSortitionSeed
}

return st.validatePrevCertificate(blk.PrevCertificate(), blk.Header().PrevBlockHash())
Expand All @@ -37,16 +40,13 @@ func (st *state) validateBlock(blk *block.Block, round int16) error {
func (st *state) validatePrevCertificate(cert *certificate.BlockCertificate, blockHash hash.Hash) error {
if cert == nil {
if !st.lastInfo.BlockHash().IsUndef() {
return errors.Errorf(errors.ErrInvalidBlock,
"only genesis block has no certificate")
return ErrInvalidCertificate
}
} else {
if cert.Round() != st.lastInfo.Certificate().Round() {
// TODO: we should panic here?
// It is impossible, unless we have a fork on the latest block
return InvalidBlockCertificateError{
Cert: cert,
}
return ErrInvalidCertificate
}

err := cert.Validate(st.lastInfo.Validators(), blockHash)
Expand Down
Loading

0 comments on commit d776f0c

Please sign in to comment.