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

Move commitment checks to block's syntactical check. #475

Merged
merged 13 commits into from
Aug 10, 2023
Merged
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
Loading