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

317: Sanitization #318

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions internal/options/error_handler_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const (
forkBombErrorScoreDefault = errorScoreThresholdDefault * 2
maxHeightErrorScoreDefault = blockApplicationErrorScoreDefault
protocolMistmatchErrorScoreDefault = errorScoreThresholdDefault * 2
invalidBlockErrorScoreDefault = errorScoreThresholdDefault / 10
invalidTransactionErrorScoreDefault = errorScoreThresholdDefault / 10
unknownErrorScoreDefault = blockApplicationErrorScoreDefault
)

Expand Down Expand Up @@ -58,6 +60,8 @@ type PeerErrorHandlerOptions struct {
ForkBombErrorScore uint64
MaxHeightErrorScore uint64
ProtocolMismatchErrorScore uint64
InvalidBlockErrorScore uint64
InvalidTransactionErrorScore uint64
UnknownErrorScore uint64
}

Expand Down Expand Up @@ -87,6 +91,8 @@ func NewPeerErrorHandlerOptions() *PeerErrorHandlerOptions {
ForkBombErrorScore: forkBombErrorScoreDefault,
MaxHeightErrorScore: maxHeightErrorScoreDefault,
ProtocolMismatchErrorScore: protocolMistmatchErrorScoreDefault,
InvalidBlockErrorScore: invalidBlockErrorScoreDefault,
InvalidTransactionErrorScore: invalidTransactionErrorScoreDefault,
UnknownErrorScore: unknownErrorScoreDefault,
}
}
48 changes: 46 additions & 2 deletions internal/p2p/applicator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package p2p
import (
"context"
"errors"
"fmt"
"sync/atomic"
"time"

Expand Down Expand Up @@ -95,10 +96,29 @@ func NewApplicator(ctx context.Context, rpc rpc.LocalRPC, cache *TransactionCach
}, nil
}

func (a *Applicator) validateBlock(block *protocol.Block) error {
if block.Id == nil {
return fmt.Errorf("%w, block id was nil", p2perrors.ErrInvalidBlock)
}

if block.Header == nil {
return fmt.Errorf("%w, block header was nil", p2perrors.ErrInvalidBlock)
}

if block.Header.Previous == nil {
return fmt.Errorf("%w, previous block was nil", p2perrors.ErrInvalidBlock)
}

return nil
}

// ApplyBlock will apply the block to the chain at the appropriate time
func (a *Applicator) ApplyBlock(ctx context.Context, block *protocol.Block) error {
err := a.forkWatchdog.Add(block)
if err != nil {
if err := a.validateBlock(block); err != nil {
return err
}

if err := a.forkWatchdog.Add(block); err != nil {
return err
}

Expand All @@ -115,7 +135,31 @@ func (a *Applicator) ApplyBlock(ctx context.Context, block *protocol.Block) erro
}
}

func (a *Applicator) validateTransaction(trx *protocol.Transaction) error {
if trx.Id == nil {
return fmt.Errorf("%w, transaction id was nil", p2perrors.ErrInvalidTransaction)
}

if trx.Header == nil {
return fmt.Errorf("%w, transaction header was nil", p2perrors.ErrInvalidTransaction)
}

if trx.Header.Payer == nil {
return fmt.Errorf("%w, transaction payer was nil", p2perrors.ErrInvalidTransaction)
}

if trx.Header.Nonce == nil {
return fmt.Errorf("%w, transaction nonce was nil", p2perrors.ErrInvalidTransaction)
}

return nil
}

func (a *Applicator) ApplyTransaction(ctx context.Context, trx *protocol.Transaction) error {
if err := a.validateTransaction(trx); err != nil {
return err
}

errChan := make(chan error, 1)

a.applyTransactionChan <- &applyTransactionRequest{trx, errChan, ctx}
Expand Down
127 changes: 127 additions & 0 deletions internal/p2p/applicator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package p2p

import (
"context"
"errors"
"testing"
"time"

Expand Down Expand Up @@ -391,10 +392,18 @@ func TestInvalidNonce(t *testing.T) {

goodTrx := &protocol.Transaction{
Id: []byte{0},
Header: &protocol.TransactionHeader{
Payer: []byte{0},
Nonce: []byte{0},
},
}

badTrx := &protocol.Transaction{
Id: []byte{1},
Header: &protocol.TransactionHeader{
Payer: []byte{0},
Nonce: []byte{0},
},
}

rpc.invalidNonceTrxs[string(badTrx.Id)] = void{}
Expand All @@ -411,3 +420,121 @@ func TestInvalidNonce(t *testing.T) {
t.Errorf("badTrx - ErrInvalidNonce expected but was not returned, was: %v", err)
}
}

func TestValidateBlock(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

rpc := applicatorTestRPC{
blocksToFail: make(map[string]void),
unlinkableBlocks: make(map[string]void),
head: []byte{0x00},
invalidNonceTrxs: make(map[string]void),
}

applicator, err := NewApplicator(ctx, &rpc, NewTransactionCache(time.Minute), *options.NewApplicatorOptions())
if err != nil {
t.Error(err)
}

block := &protocol.Block{
Id: []byte{0x01},
Header: &protocol.BlockHeader{
Height: 1,
Previous: []byte{0},
},
}

err = applicator.validateBlock(block)
if err != nil {
t.Error(err)
}

block.Header.Previous = nil
err = applicator.validateBlock(block)
if err == nil {
t.Error("validateBlock should fail with a nil previous block")
} else if !errors.Is(err, p2perrors.ErrInvalidBlock) {
t.Errorf("expected validateBlock to return ErrInvalidBlock, was: %e", err)
}

block.Header.Previous = []byte{0}
block.Id = nil
if err == nil {
t.Error("validateBlock should fail with a nil block id")
} else if !errors.Is(err, p2perrors.ErrInvalidBlock) {
t.Errorf("expected validateBlock to return ErrInvalidBlock, was: %e", err)
}

block.Id = []byte{0x01}
block.Header = nil
if err == nil {
t.Error("validateBlock should fail with a nil header")
} else if !errors.Is(err, p2perrors.ErrInvalidBlock) {
t.Errorf("expected validateBlock to return ErrInvalidBlock, was: %e", err)
}
}

func TestValidateTransaction(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

rpc := applicatorTestRPC{
blocksToFail: make(map[string]void),
unlinkableBlocks: make(map[string]void),
head: []byte{0x00},
invalidNonceTrxs: make(map[string]void),
}

applicator, err := NewApplicator(ctx, &rpc, NewTransactionCache(time.Minute), *options.NewApplicatorOptions())
if err != nil {
t.Error(err)
}

trx := &protocol.Transaction{
Id: []byte{0},
Header: &protocol.TransactionHeader{
Payer: []byte{0},
Nonce: []byte{0},
},
}

err = applicator.validateTransaction(trx)
if err != nil {
t.Error(err)
}

trx.Id = nil
err = applicator.validateTransaction(trx)
if err == nil {
t.Error("validateTransaction should fail with a nil transaction id")
} else if !errors.Is(err, p2perrors.ErrInvalidTransaction) {
t.Errorf("expected validateTransaction to return ErrInvalidTransaction, was: %e", err)
}

trx.Id = []byte{0}
trx.Header.Payer = nil
err = applicator.validateTransaction(trx)
if err == nil {
t.Error("validateTransaction should fail with a nil transaction id")
} else if !errors.Is(err, p2perrors.ErrInvalidTransaction) {
t.Errorf("expected validateTransaction to return ErrInvalidTransaction, was: %e", err)
}

trx.Header.Payer = []byte{0}
trx.Header.Nonce = nil
err = applicator.validateTransaction(trx)
if err == nil {
t.Error("validateTransaction should fail with a nil transaction nonce")
} else if !errors.Is(err, p2perrors.ErrInvalidTransaction) {
t.Errorf("expected validateTransaction to return ErrInvalidTransaction, was: %e", err)
}

trx.Header = nil
err = applicator.validateTransaction(trx)
if err == nil {
t.Error("validateTransaction should fail with a nil transaction header")
} else if !errors.Is(err, p2perrors.ErrInvalidTransaction) {
t.Errorf("expected validateTransaction to return ErrInvalidTransaction, was: %e", err)
}
}
4 changes: 4 additions & 0 deletions internal/p2p/error_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ func (p *PeerErrorHandler) getScoreForError(err error) uint64 {
return p.opts.PeerRPCTimeoutErrorScore
case errors.Is(err, p2perrors.ErrForkBomb):
return p.opts.ForkBombErrorScore
case errors.Is(err, p2perrors.ErrInvalidBlock):
return p.opts.InvalidBlockErrorScore
case errors.Is(err, p2perrors.ErrInvalidTransaction):
return p.opts.InvalidTransactionErrorScore

// These errors are expected, but result in instant disconnection
case errors.Is(err, p2perrors.ErrChainIDMismatch):
Expand Down
6 changes: 6 additions & 0 deletions internal/p2perrors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,10 @@ var (

// ErrProtocolMissing represents when a peer's protocol version is missing
ErrProtocolMissing = errors.New("protocol version is missing")

// ErrInvalidBlock represents when a block fails validation
ErrInvalidBlock = errors.New("block did not pass validation")

// ErrInvalidTransaction represents when a transaction fails validation
ErrInvalidTransaction = errors.New("transaction did not pass validation")
)