Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(state): define errors for state package #1457

Merged
merged 2 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading