Skip to content

Commit

Permalink
Merge pull request #475 from iotaledger/feat/move-commitment-checks
Browse files Browse the repository at this point in the history
Move commitment checks to block's syntactical check.
  • Loading branch information
piotrm50 authored Aug 10, 2023
2 parents 3fec1b9 + 8ee005a commit 43a6977
Show file tree
Hide file tree
Showing 6 changed files with 409 additions and 43 deletions.
2 changes: 2 additions & 0 deletions api_protocol_parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ type basicProtocolParameters struct {
// and commitments in its past-cone to ATT and lastCommittedSlot respectively.
LivenessThreshold SlotIndex `serix:"16,mapKey=livenessThreshold"`
// MinCommittableAge is the minimum age relative to the accepted tangle time slot index that a slot can be committed.
// For example, if the last accepted slot is in slot 100, and minCommittableAge=10, then the latest committed slot can be at most 100-10=90.
MinCommittableAge SlotIndex `serix:"17,mapKey=minCommittableAge"`
// MaxCommittableAge is the maximum age for a slot commitment to be included in a block relative to the slot index of the block issuing time.
// For example, if the last accepted slot is in slot 100, and maxCommittableAge=20, then the oldest referencable commitment is 100-20=80.
MaxCommittableAge SlotIndex `serix:"18,mapKey=maxCommittableAge"`
// EpochNearingThreshold is used by the epoch orchestrator to detect the slot that should trigger a new committee
// selection for the next and upcoming epoch.
Expand Down
32 changes: 1 addition & 31 deletions api_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"time"

"github.com/iotaledger/hive.go/ierrors"
"github.com/iotaledger/hive.go/lo"
"github.com/iotaledger/hive.go/serializer/v2"
"github.com/iotaledger/hive.go/serializer/v2/serix"
)
Expand Down Expand Up @@ -494,11 +493,6 @@ func V3API(protoParams ProtocolParameters) API {
serix.TypeSettings{}.WithLengthPrefixType(serix.LengthPrefixTypeAsUint16).WithArrayRules(txV3UnlocksArrRules),
))
must(api.RegisterValidators(Transaction{}, nil, func(ctx context.Context, tx Transaction) error {
// limit unlock block count = input count
if len(tx.Unlocks) != len(tx.Essence.Inputs) {
return ierrors.Errorf("unlock block count must match inputs in essence, %d vs. %d", len(tx.Unlocks), len(tx.Essence.Inputs))
}

return tx.syntacticallyValidate(v3)
}))
must(api.RegisterInterfaceObjects((*TxEssencePayload)(nil), (*TaggedData)(nil)))
Expand Down Expand Up @@ -537,31 +531,7 @@ func V3API(protoParams ProtocolParameters) API {

return nil
}, func(ctx context.Context, protocolBlock ProtocolBlock) error {
if protoParams.Version() != protocolBlock.ProtocolVersion {
return ierrors.Errorf("mismatched protocol version: wanted %d, got %d in block", protoParams.Version(), protocolBlock.ProtocolVersion)
}

block := protocolBlock.Block
if len(block.WeakParentIDs()) > 0 {
// weak parents must be disjunct to the rest of the parents
nonWeakParents := lo.KeyOnlyBy(append(block.StrongParentIDs(), block.ShallowLikeParentIDs()...), func(v BlockID) BlockID {
return v
})

for _, parent := range block.WeakParentIDs() {
if _, contains := nonWeakParents[parent]; contains {
return ierrors.Errorf("weak parents must be disjunct to the rest of the parents")
}
}
}

if validationBlock, ok := block.(*ValidationBlock); ok {
if validationBlock.HighestSupportedVersion < protocolBlock.ProtocolVersion {
return ierrors.Errorf("highest supported version %d must be greater equal protocol version %d", validationBlock.HighestSupportedVersion, protocolBlock.ProtocolVersion)
}
}

return nil
return protocolBlock.syntacticallyValidate(v3)
}))
}

Expand Down
101 changes: 101 additions & 0 deletions block.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

hiveEd25519 "github.com/iotaledger/hive.go/crypto/ed25519"
"github.com/iotaledger/hive.go/ierrors"
"github.com/iotaledger/hive.go/lo"
"github.com/iotaledger/hive.go/serializer/v2"
"github.com/iotaledger/hive.go/serializer/v2/byteutils"
"github.com/iotaledger/iota.go/v4/hexutil"
Expand All @@ -27,6 +28,16 @@ const (
BlockTypeValidationMaxParents = BlockMaxParents + 42
)

var (
ErrWeakParentsInvalid = ierrors.New("weak parents must be disjunct to the rest of the parents")
ErrCommitmentTooOld = ierrors.New("a block cannot commit to a slot that is older than the block's slot minus maxCommittableAge")
ErrCommitmentTooRecent = ierrors.New("a block cannot commit to a slot that is more recent than the block's slot minus minCommittableAge")
ErrCommitmentInputTooOld = ierrors.New("a block cannot contain a commitment input with index older than the block's slot minus maxCommittableAge")
ErrCommitmentInputTooRecent = ierrors.New("a block cannot contain a commitment input with index more recent than the block's slot minus minCommittableAge")
ErrInvalidBlockVersion = ierrors.New("block has invalid protocol version")
ErrCommitmentInputNewerThanCommitment = ierrors.New("a block cannot contain a commitment input with index newer than the commitment index")
)

// BlockType denotes a type of Block.
type BlockType byte

Expand Down Expand Up @@ -286,6 +297,49 @@ func (b *ProtocolBlock) WorkScore(workScoreStructure *WorkScoreStructure) (WorkS
return workScoreHeader.Add(workScoreBlock, workScoreSignature)
}

// syntacticallyValidate syntactically validates the ProtocolBlock.
func (b *ProtocolBlock) syntacticallyValidate(api API) error {
if api.ProtocolParameters().Version() != b.ProtocolVersion {
return ierrors.Wrapf(ErrInvalidBlockVersion, "mismatched protocol version: wanted %d, got %d in block", api.ProtocolParameters().Version(), b.ProtocolVersion)
}

block := b.Block
if len(block.WeakParentIDs()) > 0 {
// weak parents must be disjunct to the rest of the parents
nonWeakParents := lo.KeyOnlyBy(append(block.StrongParentIDs(), block.ShallowLikeParentIDs()...), func(v BlockID) BlockID {
return v
})

for _, parent := range block.WeakParentIDs() {
if _, contains := nonWeakParents[parent]; contains {
return ierrors.Wrapf(ErrWeakParentsInvalid, "weak parents (%s) cannot have common elements with strong parents (%s) or shallow likes (%s)", block.WeakParentIDs(), block.StrongParentIDs(), block.ShallowLikeParentIDs())
}
}
}

minCommittableAge := api.ProtocolParameters().MinCommittableAge()
maxCommittableAge := api.ProtocolParameters().MaxCommittableAge()
commitmentIndex := b.SlotCommitmentID.Index()
blockID, err := b.ID(api)
if err != nil {
return ierrors.Wrapf(err, "failed to syntactically validate block")
}
blockIndex := blockID.Index()

// check that commitment is not too recent.
if commitmentIndex > 0 && // Don't filter commitments to genesis based on being too recent.
blockIndex < commitmentIndex+minCommittableAge {
return ierrors.Wrapf(ErrCommitmentTooRecent, "block at slot %d committing to slot %d", blockIndex, b.SlotCommitmentID.Index())
}

// Check that commitment is not too old.
if blockIndex > commitmentIndex+maxCommittableAge {
return ierrors.Wrapf(ErrCommitmentTooOld, "block at slot %d committing to slot %d, max committable age %d", blockIndex, b.SlotCommitmentID.Index(), maxCommittableAge)
}

return b.Block.syntacticallyValidate(api, b)
}

type Block interface {
Type() BlockType

Expand All @@ -295,6 +349,8 @@ type Block interface {

Hash(api API) (Identifier, error)

syntacticallyValidate(api API, protocolBlock *ProtocolBlock) error

ProcessableObject
}

Expand Down Expand Up @@ -361,6 +417,42 @@ func (b *BasicBlock) WorkScore(workScoreStructure *WorkScoreStructure) (WorkScor
return workScoreBytes.Add(workScoreMissingParents, workScorePayload)
}

// syntacticallyValidate syntactically validates the BasicBlock.
func (b *BasicBlock) syntacticallyValidate(api API, protocolBlock *ProtocolBlock) error {
if b.Payload != nil && b.Payload.PayloadType() == PayloadTransaction {
blockID, err := protocolBlock.ID(api)
if err != nil {
// TODO: wrap error
return err
}
blockIndex := blockID.Index()

minCommittableAge := api.ProtocolParameters().MinCommittableAge()
maxCommittableAge := api.ProtocolParameters().MaxCommittableAge()

tx, _ := b.Payload.(*Transaction)
if cInput := tx.CommitmentInput(); cInput != nil {
cInputIndex := cInput.CommitmentID.Index()
// check that commitment input is not too recent.
if cInputIndex > 0 && // Don't filter commitments to genesis based on being too recent.
blockIndex < cInputIndex+minCommittableAge { // filter commitments to future slots.
return ierrors.Wrapf(ErrCommitmentInputTooRecent, "block at slot %d with commitment input to slot %d", blockIndex, cInput.CommitmentID.Index())
}
// Check that commitment input is not too old.
if blockIndex > cInputIndex+maxCommittableAge {
return ierrors.Wrapf(ErrCommitmentInputTooOld, "block at slot %d committing to slot %d, max committable age %d", blockIndex, cInput.CommitmentID.Index(), maxCommittableAge)
}

if cInputIndex > protocolBlock.SlotCommitmentID.Index() {
return ierrors.Wrapf(ErrCommitmentInputNewerThanCommitment, "transaction in a block contains CommitmentInput to slot %d while max allowed is %d", cInput.CommitmentID.Index(), protocolBlock.SlotCommitmentID.Index())
}

}
}

return nil
}

// ValidationBlock represents a validation vertex in the Tangle/BlockDAG.
type ValidationBlock struct {
// The parents the block references.
Expand Down Expand Up @@ -403,6 +495,15 @@ func (b *ValidationBlock) WorkScore(_ *WorkScoreStructure) (WorkScore, error) {
return 0, nil
}

// syntacticallyValidate syntactically validates the ValidationBlock.
func (b *ValidationBlock) syntacticallyValidate(_ API, protocolBlock *ProtocolBlock) error {
if b.HighestSupportedVersion < protocolBlock.ProtocolVersion {
return ierrors.Errorf("highest supported version %d must be greater equal protocol version %d", b.HighestSupportedVersion, protocolBlock.ProtocolVersion)
}

return nil
}

// ParentsType is a type that defines the type of the parent.
type ParentsType uint8

Expand Down
Loading

0 comments on commit 43a6977

Please sign in to comment.