From b2ba86beae0f49c7d314f6aa84f9621bcb8fa51e Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Fri, 4 Aug 2023 15:06:20 +0200 Subject: [PATCH 01/12] Move commitment checks to block's syntactical check. --- api_v3.go | 32 +-------------- block.go | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ block_test.go | 62 ++++++++++++++++++++++------- transaction.go | 5 +++ 4 files changed, 159 insertions(+), 44 deletions(-) diff --git a/api_v3.go b/api_v3.go index 169d4656d..14874576e 100644 --- a/api_v3.go +++ b/api_v3.go @@ -5,7 +5,6 @@ import ( "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/serix" ) @@ -485,11 +484,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))) @@ -528,31 +522,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) })) } diff --git a/block.go b/block.go index 5cfd339fe..cfd079959 100644 --- a/block.go +++ b/block.go @@ -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" @@ -27,6 +28,15 @@ const ( BlockTypeValidationMaxParents = BlockMaxParents + 42 ) +var ( + 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 @@ -286,6 +296,52 @@ 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.Errorf("weak parents must be disjunct to the rest of the parents") + } + } + } + + minCommittableAge := api.ProtocolParameters().MinCommittableAge() + maxCommittableAge := api.ProtocolParameters().MaxCommittableAge() + commitmentIndex := b.SlotCommitmentID.Index() + blockID, err := b.ID(api) + if err != nil { + // TODO: wrap error + return err + } + blockIndex := blockID.Index() + + // check that commitment is not too recent. + if minCommittableAge > 0 && // don't filter anything for being too recent if minCommittableAge is 0 + 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()) + } + + // TODO: move to syntactical check + // 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 @@ -295,6 +351,8 @@ type Block interface { Hash(api API) (Identifier, error) + syntacticallyValidate(api API, protocolBlock *ProtocolBlock) error + ProcessableObject } @@ -361,6 +419,43 @@ 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 minCommittableAge > 0 && // don't filter anything for being too recent if minCommittableAge is 0 + 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 cInput.CommitmentID.Index() > 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. @@ -403,6 +498,15 @@ func (b *ValidationBlock) WorkScore(_ *WorkScoreStructure) (WorkScore, error) { return 0, nil } +// syntacticallyValidate syntactically validates the ValidationBlock. +func (b *ValidationBlock) syntacticallyValidate(api 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 diff --git a/block_test.go b/block_test.go index 7d9675c79..899e5d36e 100644 --- a/block_test.go +++ b/block_test.go @@ -6,9 +6,13 @@ import ( "github.com/stretchr/testify/require" + "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/hive.go/serializer/v2" "github.com/iotaledger/hive.go/serializer/v2/serix" + "github.com/iotaledger/iota-core/pkg/model" iotago "github.com/iotaledger/iota.go/v4" + "github.com/iotaledger/iota.go/v4/api" + "github.com/iotaledger/iota.go/v4/builder" "github.com/iotaledger/iota.go/v4/tpkg" ) @@ -47,21 +51,53 @@ func TestBlock_DeSerialize(t *testing.T) { } } +func createBlockAtSlotWithVersion(t *testing.T, index iotago.SlotIndex, version iotago.Version, apiProvider *api.EpochBasedProvider) error { + api := apiProvider.APIForSlot(index) + block, err := builder.NewBasicBlockBuilder(api). + ProtocolVersion(version). + StrongParents(iotago.BlockIDs{iotago.BlockID{}}). + IssuingTime(api.TimeProvider().SlotStartTime(index)). + SlotCommitmentID(iotago.NewCommitment(api.Version(), index-api.ProtocolParameters().MinCommittableAge(), iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). + Build() + require.NoError(t, err) + + return lo.Return2(model.BlockFromBlock(block, api, serix.WithValidation())) +} + func TestProtocolBlock_ProtocolVersionSyntactical(t *testing.T) { - block := &iotago.ProtocolBlock{ - BlockHeader: iotago.BlockHeader{ - ProtocolVersion: tpkg.TestAPI.Version() + 1, - SlotCommitmentID: iotago.NewEmptyCommitment(tpkg.TestAPI.Version()).MustID(), - }, - Signature: tpkg.RandEd25519Signature(), - Block: &iotago.BasicBlock{ - StrongParents: tpkg.SortedRandBlockIDs(1), - Payload: nil, - }, - } + apiProvider := api.NewEpochBasedProvider( + api.WithAPIForMissingVersionCallback( + func(version iotago.Version) (iotago.API, error) { + return iotago.V3API(iotago.NewV3ProtocolParameters(iotago.WithVersion(version))), nil + }, + ), + ) + apiProvider.AddProtocolParametersAtEpoch(iotago.NewV3ProtocolParameters(), 0) + apiProvider.AddProtocolParametersAtEpoch(iotago.NewV3ProtocolParameters(iotago.WithVersion(4)), 3) + + timeProvider := apiProvider.CurrentAPI().TimeProvider() + + require.ErrorIs(t, createBlockAtSlotWithVersion(t, timeProvider.EpochStart(1), 2, apiProvider), iotago.ErrInvalidBlockVersion) + + require.NoError(t, createBlockAtSlotWithVersion(t, timeProvider.EpochEnd(1), 3, apiProvider)) + + require.NoError(t, createBlockAtSlotWithVersion(t, timeProvider.EpochEnd(2), 3, apiProvider)) + + require.ErrorIs(t, createBlockAtSlotWithVersion(t, timeProvider.EpochStart(3), 3, apiProvider), iotago.ErrInvalidBlockVersion) + + require.NoError(t, createBlockAtSlotWithVersion(t, timeProvider.EpochStart(3), 4, apiProvider)) + + require.NoError(t, createBlockAtSlotWithVersion(t, timeProvider.EpochEnd(3), 4, apiProvider)) + + require.NoError(t, createBlockAtSlotWithVersion(t, timeProvider.EpochStart(5), 4, apiProvider)) + + apiProvider.AddProtocolParametersAtEpoch(iotago.NewV3ProtocolParameters(iotago.WithVersion(5)), 10) + + require.NoError(t, createBlockAtSlotWithVersion(t, timeProvider.EpochEnd(9), 4, apiProvider)) + + require.ErrorIs(t, createBlockAtSlotWithVersion(t, timeProvider.EpochStart(10), 4, apiProvider), iotago.ErrInvalidBlockVersion) - _, err := tpkg.TestAPI.Encode(block, serix.WithValidation()) - require.ErrorContains(t, err, "mismatched protocol version") + require.NoError(t, createBlockAtSlotWithVersion(t, timeProvider.EpochStart(10), 5, apiProvider)) } func TestProtocolBlock_DeserializationNotEnoughData(t *testing.T) { diff --git a/transaction.go b/transaction.go index ee2c8a984..8a631830e 100644 --- a/transaction.go +++ b/transaction.go @@ -155,6 +155,11 @@ func (t *Transaction) String() string { // syntacticallyValidate syntactically validates the Transaction. func (t *Transaction) syntacticallyValidate(api API) error { + // limit unlock block count = input count + if len(t.Unlocks) != len(t.Essence.Inputs) { + return ierrors.Errorf("unlock block count must match inputs in essence, %d vs. %d", len(t.Unlocks), len(t.Essence.Inputs)) + } + if err := t.Essence.syntacticallyValidate(api.ProtocolParameters()); err != nil { return ierrors.Errorf("transaction essence is invalid: %w", err) } From 7edc0d5656a9be18632b6feab048c9f65ac5ef37 Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Fri, 4 Aug 2023 15:11:54 +0200 Subject: [PATCH 02/12] Fix block serialization. --- block_test.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/block_test.go b/block_test.go index 899e5d36e..cc9dc23ae 100644 --- a/block_test.go +++ b/block_test.go @@ -9,7 +9,6 @@ import ( "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/hive.go/serializer/v2" "github.com/iotaledger/hive.go/serializer/v2/serix" - "github.com/iotaledger/iota-core/pkg/model" iotago "github.com/iotaledger/iota.go/v4" "github.com/iotaledger/iota.go/v4/api" "github.com/iotaledger/iota.go/v4/builder" @@ -52,16 +51,16 @@ func TestBlock_DeSerialize(t *testing.T) { } func createBlockAtSlotWithVersion(t *testing.T, index iotago.SlotIndex, version iotago.Version, apiProvider *api.EpochBasedProvider) error { - api := apiProvider.APIForSlot(index) - block, err := builder.NewBasicBlockBuilder(api). + apiForSlot := apiProvider.APIForSlot(index) + block, err := builder.NewBasicBlockBuilder(apiForSlot). ProtocolVersion(version). StrongParents(iotago.BlockIDs{iotago.BlockID{}}). - IssuingTime(api.TimeProvider().SlotStartTime(index)). - SlotCommitmentID(iotago.NewCommitment(api.Version(), index-api.ProtocolParameters().MinCommittableAge(), iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). + IssuingTime(apiForSlot.TimeProvider().SlotStartTime(index)). + SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), index-apiForSlot.ProtocolParameters().MinCommittableAge(), iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). Build() require.NoError(t, err) - return lo.Return2(model.BlockFromBlock(block, api, serix.WithValidation())) + return lo.Return2(apiForSlot.Encode(block, serix.WithValidation())) } func TestProtocolBlock_ProtocolVersionSyntactical(t *testing.T) { From c45793cd8d206e74312105bab93e77cdda6abf19 Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Fri, 4 Aug 2023 15:16:29 +0200 Subject: [PATCH 03/12] Fix linter problems. --- block.go | 2 +- block_test.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/block.go b/block.go index cfd079959..711505197 100644 --- a/block.go +++ b/block.go @@ -499,7 +499,7 @@ func (b *ValidationBlock) WorkScore(_ *WorkScoreStructure) (WorkScore, error) { } // syntacticallyValidate syntactically validates the ValidationBlock. -func (b *ValidationBlock) syntacticallyValidate(api API, protocolBlock *ProtocolBlock) error { +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) } diff --git a/block_test.go b/block_test.go index cc9dc23ae..2a55882f1 100644 --- a/block_test.go +++ b/block_test.go @@ -51,6 +51,8 @@ func TestBlock_DeSerialize(t *testing.T) { } func createBlockAtSlotWithVersion(t *testing.T, index iotago.SlotIndex, version iotago.Version, apiProvider *api.EpochBasedProvider) error { + t.Helper() + apiForSlot := apiProvider.APIForSlot(index) block, err := builder.NewBasicBlockBuilder(apiForSlot). ProtocolVersion(version). From 169784bafa9fc6dabe2b4d24a6411d318d03665e Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Mon, 7 Aug 2023 14:19:50 +0200 Subject: [PATCH 04/12] Improve block syntactical check and move commitment tests from iota-core. --- block.go | 9 +- block_test.go | 234 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 234 insertions(+), 9 deletions(-) diff --git a/block.go b/block.go index 711505197..2e665bed8 100644 --- a/block.go +++ b/block.go @@ -29,6 +29,7 @@ const ( ) 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") @@ -311,7 +312,7 @@ func (b *ProtocolBlock) syntacticallyValidate(api API) error { for _, parent := range block.WeakParentIDs() { if _, contains := nonWeakParents[parent]; contains { - return ierrors.Errorf("weak parents must be disjunct to the rest of the parents") + 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()) } } } @@ -321,8 +322,7 @@ func (b *ProtocolBlock) syntacticallyValidate(api API) error { commitmentIndex := b.SlotCommitmentID.Index() blockID, err := b.ID(api) if err != nil { - // TODO: wrap error - return err + return ierrors.Wrapf(err, "failed to syntactically validate block") } blockIndex := blockID.Index() @@ -333,7 +333,6 @@ func (b *ProtocolBlock) syntacticallyValidate(api API) error { return ierrors.Wrapf(ErrCommitmentTooRecent, "block at slot %d committing to slot %d", blockIndex, b.SlotCommitmentID.Index()) } - // TODO: move to syntactical check // 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) @@ -446,7 +445,7 @@ func (b *BasicBlock) syntacticallyValidate(api API, protocolBlock *ProtocolBlock return ierrors.Wrapf(ErrCommitmentInputTooOld, "block at slot %d committing to slot %d, max committable age %d", blockIndex, cInput.CommitmentID.Index(), maxCommittableAge) } - if cInput.CommitmentID.Index() > protocolBlock.SlotCommitmentID.Index() { + 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()) } diff --git a/block_test.go b/block_test.go index 2a55882f1..09be1fcb6 100644 --- a/block_test.go +++ b/block_test.go @@ -1,14 +1,18 @@ package iotago_test import ( + "crypto/ed25519" "fmt" "testing" + "time" "github.com/stretchr/testify/require" + hiveEd25519 "github.com/iotaledger/hive.go/crypto/ed25519" "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/hive.go/serializer/v2" "github.com/iotaledger/hive.go/serializer/v2/serix" + "github.com/iotaledger/iota-core/pkg/utils" iotago "github.com/iotaledger/iota.go/v4" "github.com/iotaledger/iota.go/v4/api" "github.com/iotaledger/iota.go/v4/builder" @@ -50,15 +54,57 @@ func TestBlock_DeSerialize(t *testing.T) { } } -func createBlockAtSlotWithVersion(t *testing.T, index iotago.SlotIndex, version iotago.Version, apiProvider *api.EpochBasedProvider) error { +func createBlockWithParents(t *testing.T, strongParents, weakParents, shallowLikeParent iotago.BlockIDs, apiProvider *api.EpochBasedProvider) error { + apiForSlot := apiProvider.LatestAPI() + + block, err := builder.NewBasicBlockBuilder(apiForSlot). + StrongParents(strongParents). + WeakParents(weakParents). + ShallowLikeParents(shallowLikeParent). + IssuingTime(time.Now()). + SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), apiForSlot.TimeProvider().SlotFromTime(time.Now())-apiForSlot.ProtocolParameters().MinCommittableAge(), iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). + Build() + require.NoError(t, err) + + return lo.Return2(apiForSlot.Encode(block, serix.WithValidation())) +} + +func createBlockAtSlot(t *testing.T, blockIndex, commitmentIndex iotago.SlotIndex, apiProvider *api.EpochBasedProvider) error { + apiForSlot := apiProvider.APIForSlot(blockIndex) + + block, err := builder.NewBasicBlockBuilder(apiForSlot). + StrongParents(iotago.BlockIDs{tpkg.RandBlockID()}). + IssuingTime(apiForSlot.TimeProvider().SlotStartTime(blockIndex)). + SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), commitmentIndex, iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). + Build() + require.NoError(t, err) + + return lo.Return2(apiForSlot.Encode(block, serix.WithValidation())) +} + +func createBlockAtSlotWithVersion(t *testing.T, blockIndex iotago.SlotIndex, version iotago.Version, apiProvider *api.EpochBasedProvider) error { t.Helper() - apiForSlot := apiProvider.APIForSlot(index) + apiForSlot := apiProvider.APIForSlot(blockIndex) block, err := builder.NewBasicBlockBuilder(apiForSlot). ProtocolVersion(version). StrongParents(iotago.BlockIDs{iotago.BlockID{}}). - IssuingTime(apiForSlot.TimeProvider().SlotStartTime(index)). - SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), index-apiForSlot.ProtocolParameters().MinCommittableAge(), iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). + IssuingTime(apiForSlot.TimeProvider().SlotStartTime(blockIndex)). + SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), blockIndex-apiForSlot.ProtocolParameters().MinCommittableAge(), iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). + Build() + require.NoError(t, err) + + return lo.Return2(apiForSlot.Encode(block, serix.WithValidation())) +} + +func createBlockAtSlotWithPayload(t *testing.T, blockIndex, commitmentIndex iotago.SlotIndex, payload iotago.Payload, apiProvider *api.EpochBasedProvider) error { + apiForSlot := apiProvider.APIForSlot(blockIndex) + + block, err := builder.NewBasicBlockBuilder(apiForSlot). + StrongParents(iotago.BlockIDs{tpkg.RandBlockID()}). + IssuingTime(apiForSlot.TimeProvider().SlotStartTime(blockIndex)). + SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), commitmentIndex, iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). + Payload(payload). Build() require.NoError(t, err) @@ -101,6 +147,186 @@ func TestProtocolBlock_ProtocolVersionSyntactical(t *testing.T) { require.NoError(t, createBlockAtSlotWithVersion(t, timeProvider.EpochStart(10), 5, apiProvider)) } +func TestProtocolBlock_Commitments(t *testing.T) { + // with the following parameters, a block issued in slot 100 can commit between slot 80 and 90 + apiProvider := api.NewEpochBasedProvider() + apiProvider.AddProtocolParametersAtEpoch( + iotago.NewV3ProtocolParameters( + iotago.WithTimeProviderOptions(time.Now().Add(-20*time.Minute).Unix(), 10, 13), + iotago.WithLivenessOptions(3, 10, 20, 4), + ), 0) + + require.ErrorIs(t, createBlockAtSlot(t, 100, 79, apiProvider), iotago.ErrCommitmentTooOld) + + require.ErrorIs(t, createBlockAtSlot(t, 100, 91, apiProvider), iotago.ErrCommitmentTooRecent) + + require.NoError(t, createBlockAtSlot(t, 100, 90, apiProvider)) + + require.NoError(t, createBlockAtSlot(t, 100, 80, apiProvider)) + + require.NoError(t, createBlockAtSlot(t, 100, 85, apiProvider)) +} + +func TestProtocolBlock_WeakParents(t *testing.T) { + // with the following parameters, a block issued in slot 100 can commit between slot 80 and 90 + apiProvider := api.NewEpochBasedProvider() + apiProvider.AddProtocolParametersAtEpoch( + iotago.NewV3ProtocolParameters( + iotago.WithTimeProviderOptions(time.Now().Add(-20*time.Minute).Unix(), 10, 13), + iotago.WithLivenessOptions(3, 10, 20, 4), + ), 0) + strongParent1 := tpkg.RandBlockID() + strongParent2 := tpkg.RandBlockID() + weakParent1 := tpkg.RandBlockID() + weakParent2 := tpkg.RandBlockID() + shallowLikeParent1 := tpkg.RandBlockID() + shallowLikeParent2 := tpkg.RandBlockID() + require.ErrorIs(t, createBlockWithParents( + t, + iotago.BlockIDs{strongParent1, strongParent2}, + iotago.BlockIDs{weakParent1, weakParent2, shallowLikeParent2}, + iotago.BlockIDs{shallowLikeParent1, shallowLikeParent2}, + apiProvider, + ), iotago.ErrWeakParentsInvalid) + + require.ErrorIs(t, createBlockWithParents( + t, + iotago.BlockIDs{strongParent1, strongParent2}, + iotago.BlockIDs{weakParent1, weakParent2, strongParent2}, + iotago.BlockIDs{shallowLikeParent1, shallowLikeParent2}, + apiProvider, + ), iotago.ErrWeakParentsInvalid) + + require.NoError(t, createBlockWithParents( + t, + iotago.BlockIDs{strongParent1, strongParent2}, + iotago.BlockIDs{weakParent1, weakParent2}, + iotago.BlockIDs{shallowLikeParent1, shallowLikeParent2}, + apiProvider, + )) + + require.NoError(t, createBlockWithParents( + t, + iotago.BlockIDs{strongParent1, strongParent2}, + iotago.BlockIDs{weakParent1, weakParent2}, + iotago.BlockIDs{shallowLikeParent1, shallowLikeParent2, strongParent2}, + apiProvider, + )) +} + +func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { + keyPair := hiveEd25519.GenerateKeyPair() + // We derive a dummy account from addr. + addr := iotago.Ed25519AddressFromPubKey(keyPair.PublicKey[:]) + + // with the following parameters, block issued in slot 110 can contain a transaction with commitment input referencing + // commitments between 90 and slot that the block commits to (100 at most) + apiProvider := api.NewEpochBasedProvider() + apiProvider.AddProtocolParametersAtEpoch( + iotago.NewV3ProtocolParameters( + iotago.WithTimeProviderOptions(time.Now().Add(-20*time.Minute).Unix(), 10, 13), + iotago.WithLivenessOptions(3, 10, 20, 4), + ), 0) + + commitmentInputTooOld, err := builder.NewTransactionBuilder(apiProvider.LatestAPI()). + AddInput(&builder.TxInput{ + UnlockTarget: addr, + InputID: tpkg.RandOutputID(0), + Input: utils.RandOutput(iotago.OutputBasic), + }). + AddOutput(utils.RandOutput(iotago.OutputBasic)). + AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(79, tpkg.Rand32ByteArray())}). + Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) + + require.NoError(t, err) + + require.ErrorIs(t, createBlockAtSlotWithPayload(t, 100, 80, commitmentInputTooOld, apiProvider), iotago.ErrCommitmentInputTooOld) + + commitmentInputTooRecent, err := builder.NewTransactionBuilder(apiProvider.LatestAPI()). + AddInput(&builder.TxInput{ + UnlockTarget: addr, + InputID: tpkg.RandOutputID(0), + Input: utils.RandOutput(iotago.OutputBasic), + }). + AddOutput(utils.RandOutput(iotago.OutputBasic)). + AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(91, tpkg.Rand32ByteArray())}). + Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) + + require.NoError(t, err) + + require.ErrorIs(t, createBlockAtSlotWithPayload(t, 100, 90, commitmentInputTooRecent, apiProvider), iotago.ErrCommitmentInputTooRecent) + + commitmentInputNewerThanBlockCommitment, err := builder.NewTransactionBuilder(apiProvider.LatestAPI()). + AddInput(&builder.TxInput{ + UnlockTarget: addr, + InputID: tpkg.RandOutputID(0), + Input: utils.RandOutput(iotago.OutputBasic), + }). + AddOutput(utils.RandOutput(iotago.OutputBasic)). + AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(85, tpkg.Rand32ByteArray())}). + Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) + + require.NoError(t, err) + + require.ErrorIs(t, createBlockAtSlotWithPayload(t, 100, 80, commitmentInputNewerThanBlockCommitment, apiProvider), iotago.ErrCommitmentInputNewerThanCommitment) + + commitmentCorrect, err := builder.NewTransactionBuilder(apiProvider.LatestAPI()). + AddInput(&builder.TxInput{ + UnlockTarget: addr, + InputID: tpkg.RandOutputID(0), + Input: utils.RandOutput(iotago.OutputBasic), + }). + AddOutput(utils.RandOutput(iotago.OutputBasic)). + AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(80, tpkg.Rand32ByteArray())}). + Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) + + require.NoError(t, err) + + require.NoError(t, createBlockAtSlotWithPayload(t, 100, 90, commitmentCorrect, apiProvider)) + + commitmentCorrectOldest, err := builder.NewTransactionBuilder(apiProvider.LatestAPI()). + AddInput(&builder.TxInput{ + UnlockTarget: addr, + InputID: tpkg.RandOutputID(0), + Input: utils.RandOutput(iotago.OutputBasic), + }). + AddOutput(utils.RandOutput(iotago.OutputBasic)). + AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(80, tpkg.Rand32ByteArray())}). + Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) + + require.NoError(t, err) + + require.NoError(t, createBlockAtSlotWithPayload(t, 100, 80, commitmentCorrectOldest, apiProvider)) + + commitmentCorrectNewest, err := builder.NewTransactionBuilder(apiProvider.LatestAPI()). + AddInput(&builder.TxInput{ + UnlockTarget: addr, + InputID: tpkg.RandOutputID(0), + Input: utils.RandOutput(iotago.OutputBasic), + }). + AddOutput(utils.RandOutput(iotago.OutputBasic)). + AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(90, tpkg.Rand32ByteArray())}). + Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) + + require.NoError(t, err) + + require.NoError(t, createBlockAtSlotWithPayload(t, 100, 90, commitmentCorrectNewest, apiProvider)) + + commitmentCorrectMiddle, err := builder.NewTransactionBuilder(apiProvider.LatestAPI()). + AddInput(&builder.TxInput{ + UnlockTarget: addr, + InputID: tpkg.RandOutputID(0), + Input: utils.RandOutput(iotago.OutputBasic), + }). + AddOutput(utils.RandOutput(iotago.OutputBasic)). + AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(85, tpkg.Rand32ByteArray())}). + Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) + + require.NoError(t, err) + + require.NoError(t, createBlockAtSlotWithPayload(t, 100, 90, commitmentCorrectMiddle, apiProvider)) +} + func TestProtocolBlock_DeserializationNotEnoughData(t *testing.T) { blockBytes := []byte{byte(tpkg.TestAPI.Version()), 1} From 23f355a26273edfb907196ae93b6d58cc4e5d415 Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:10:08 +0200 Subject: [PATCH 05/12] Fix test --- block_test.go | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/block_test.go b/block_test.go index 09be1fcb6..3cb550d63 100644 --- a/block_test.go +++ b/block_test.go @@ -12,7 +12,6 @@ import ( "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/hive.go/serializer/v2" "github.com/iotaledger/hive.go/serializer/v2/serix" - "github.com/iotaledger/iota-core/pkg/utils" iotago "github.com/iotaledger/iota.go/v4" "github.com/iotaledger/iota.go/v4/api" "github.com/iotaledger/iota.go/v4/builder" @@ -218,7 +217,14 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { keyPair := hiveEd25519.GenerateKeyPair() // We derive a dummy account from addr. addr := iotago.Ed25519AddressFromPubKey(keyPair.PublicKey[:]) - + output := &iotago.BasicOutput{ + Amount: 1, + Conditions: iotago.BasicOutputUnlockConditions{ + &iotago.AddressUnlockCondition{ + Address: addr, + }, + }, + } // with the following parameters, block issued in slot 110 can contain a transaction with commitment input referencing // commitments between 90 and slot that the block commits to (100 at most) apiProvider := api.NewEpochBasedProvider() @@ -232,9 +238,9 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { AddInput(&builder.TxInput{ UnlockTarget: addr, InputID: tpkg.RandOutputID(0), - Input: utils.RandOutput(iotago.OutputBasic), + Input: output, }). - AddOutput(utils.RandOutput(iotago.OutputBasic)). + AddOutput(output). AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(79, tpkg.Rand32ByteArray())}). Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) @@ -246,9 +252,9 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { AddInput(&builder.TxInput{ UnlockTarget: addr, InputID: tpkg.RandOutputID(0), - Input: utils.RandOutput(iotago.OutputBasic), + Input: output, }). - AddOutput(utils.RandOutput(iotago.OutputBasic)). + AddOutput(output). AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(91, tpkg.Rand32ByteArray())}). Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) @@ -260,9 +266,9 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { AddInput(&builder.TxInput{ UnlockTarget: addr, InputID: tpkg.RandOutputID(0), - Input: utils.RandOutput(iotago.OutputBasic), + Input: output, }). - AddOutput(utils.RandOutput(iotago.OutputBasic)). + AddOutput(output). AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(85, tpkg.Rand32ByteArray())}). Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) @@ -274,9 +280,9 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { AddInput(&builder.TxInput{ UnlockTarget: addr, InputID: tpkg.RandOutputID(0), - Input: utils.RandOutput(iotago.OutputBasic), + Input: output, }). - AddOutput(utils.RandOutput(iotago.OutputBasic)). + AddOutput(output). AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(80, tpkg.Rand32ByteArray())}). Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) @@ -288,9 +294,9 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { AddInput(&builder.TxInput{ UnlockTarget: addr, InputID: tpkg.RandOutputID(0), - Input: utils.RandOutput(iotago.OutputBasic), + Input: output, }). - AddOutput(utils.RandOutput(iotago.OutputBasic)). + AddOutput(output). AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(80, tpkg.Rand32ByteArray())}). Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) @@ -302,9 +308,9 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { AddInput(&builder.TxInput{ UnlockTarget: addr, InputID: tpkg.RandOutputID(0), - Input: utils.RandOutput(iotago.OutputBasic), + Input: output, }). - AddOutput(utils.RandOutput(iotago.OutputBasic)). + AddOutput(output). AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(90, tpkg.Rand32ByteArray())}). Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) @@ -316,9 +322,9 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { AddInput(&builder.TxInput{ UnlockTarget: addr, InputID: tpkg.RandOutputID(0), - Input: utils.RandOutput(iotago.OutputBasic), + Input: output, }). - AddOutput(utils.RandOutput(iotago.OutputBasic)). + AddOutput(output). AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(85, tpkg.Rand32ByteArray())}). Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) From 040fda7af89713535187e64d1f06722a3a654275 Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:28:23 +0200 Subject: [PATCH 06/12] Fix off-by-one error in commitment check. --- block.go | 29 +++++++++++++++++++++-------- block_test.go | 49 +++++++++++++++++++++++++++++++------------------ 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/block.go b/block.go index 2e665bed8..40b485abe 100644 --- a/block.go +++ b/block.go @@ -328,13 +328,19 @@ func (b *ProtocolBlock) syntacticallyValidate(api API) error { // check that commitment is not too recent. if minCommittableAge > 0 && // don't filter anything for being too recent if minCommittableAge is 0 - commitmentIndex > 0 && // don't filter commitments to genesis based on being too recent - blockIndex < commitmentIndex+minCommittableAge { + commitmentIndex > 0 && // Don't filter commitments to genesis based on being too recent. + // The "+1" element is there because we're comparing against 'blockIndex' which is in the middle of a slot + // and the oldest possible committed slot is 'minCommittableAge' full slots in the past. + // So we need to subtract 1 to account for the blockIndex slot that is not finished yet. + blockIndex < commitmentIndex+minCommittableAge+1 { 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 { + // Check that commitment is not too old. + // The "+1" element is there because we're comparing against 'blockIndex' which is in the middle of a slot + // and the oldest possible committed slot is 'maxCommittableAge' full slots in the past. + // So we need to subtract 1 to account for the blockIndex slot that is not finished yet. + if blockIndex > commitmentIndex+maxCommittableAge+1 { return ierrors.Wrapf(ErrCommitmentTooOld, "block at slot %d committing to slot %d, max committable age %d", blockIndex, b.SlotCommitmentID.Index(), maxCommittableAge) } @@ -436,12 +442,19 @@ func (b *BasicBlock) syntacticallyValidate(api API, protocolBlock *ProtocolBlock cInputIndex := cInput.CommitmentID.Index() // check that commitment input is not too recent. if minCommittableAge > 0 && // don't filter anything for being too recent if minCommittableAge is 0 - cInputIndex > 0 && // don't filter commitments to genesis based on being too recent - blockIndex < cInputIndex+minCommittableAge { // filter commitments to future slots + cInputIndex > 0 && // Don't filter commitments to genesis based on being too recent. + // The "+1" element is there because we're comparing against 'blockIndex' which is in the middle of a slot + // and the latest possible committed slot is 'minCommittableAge' full slots in the past. + // So we need to subtract 1 to account for the blockIndex slot that is not finished yet. + blockIndex < cInputIndex+minCommittableAge+1 { // 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 { + // Check that commitment input is not too old. + // The "+1" element is there because we're comparing against 'blockIndex' which is in the middle of a slot + // and the oldest possible committed slot is 'maxCommittableAge' full slots in the past. + // So we need to subtract 1 to account for the blockIndex slot that is not finished yet. + if blockIndex > cInputIndex+maxCommittableAge+1 { return ierrors.Wrapf(ErrCommitmentInputTooOld, "block at slot %d committing to slot %d, max committable age %d", blockIndex, cInput.CommitmentID.Index(), maxCommittableAge) } diff --git a/block_test.go b/block_test.go index 3cb550d63..ee72e2fee 100644 --- a/block_test.go +++ b/block_test.go @@ -61,7 +61,7 @@ func createBlockWithParents(t *testing.T, strongParents, weakParents, shallowLik WeakParents(weakParents). ShallowLikeParents(shallowLikeParent). IssuingTime(time.Now()). - SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), apiForSlot.TimeProvider().SlotFromTime(time.Now())-apiForSlot.ProtocolParameters().MinCommittableAge(), iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). + SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), apiForSlot.TimeProvider().SlotFromTime(time.Now())-1-apiForSlot.ProtocolParameters().MinCommittableAge(), iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). Build() require.NoError(t, err) @@ -89,7 +89,7 @@ func createBlockAtSlotWithVersion(t *testing.T, blockIndex iotago.SlotIndex, ver ProtocolVersion(version). StrongParents(iotago.BlockIDs{iotago.BlockID{}}). IssuingTime(apiForSlot.TimeProvider().SlotStartTime(blockIndex)). - SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), blockIndex-apiForSlot.ProtocolParameters().MinCommittableAge(), iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). + SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), blockIndex-1-apiForSlot.ProtocolParameters().MinCommittableAge(), iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). Build() require.NoError(t, err) @@ -155,17 +155,30 @@ func TestProtocolBlock_Commitments(t *testing.T) { iotago.WithLivenessOptions(3, 10, 20, 4), ), 0) - require.ErrorIs(t, createBlockAtSlot(t, 100, 79, apiProvider), iotago.ErrCommitmentTooOld) + require.ErrorIs(t, createBlockAtSlot(t, 100, 78, apiProvider), iotago.ErrCommitmentTooOld) - require.ErrorIs(t, createBlockAtSlot(t, 100, 91, apiProvider), iotago.ErrCommitmentTooRecent) + require.ErrorIs(t, createBlockAtSlot(t, 100, 90, apiProvider), iotago.ErrCommitmentTooRecent) - require.NoError(t, createBlockAtSlot(t, 100, 90, apiProvider)) + require.NoError(t, createBlockAtSlot(t, 100, 89, apiProvider)) require.NoError(t, createBlockAtSlot(t, 100, 80, apiProvider)) require.NoError(t, createBlockAtSlot(t, 100, 85, apiProvider)) } +func TestProtocolBlock_Commitments1(t *testing.T) { + // with the following parameters, a block issued in slot 100 can commit between slot 80 and 90 + apiProvider := api.NewEpochBasedProvider() + apiProvider.AddProtocolParametersAtEpoch( + iotago.NewV3ProtocolParameters( + iotago.WithTimeProviderOptions(time.Now().Add(-20*time.Minute).Unix(), 10, 13), + iotago.WithLivenessOptions(3, 6, 20, 4), + ), 0) + + require.ErrorIs(t, createBlockAtSlot(t, 10, 4, apiProvider), iotago.ErrCommitmentTooRecent) + +} + func TestProtocolBlock_WeakParents(t *testing.T) { // with the following parameters, a block issued in slot 100 can commit between slot 80 and 90 apiProvider := api.NewEpochBasedProvider() @@ -218,7 +231,7 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { // We derive a dummy account from addr. addr := iotago.Ed25519AddressFromPubKey(keyPair.PublicKey[:]) output := &iotago.BasicOutput{ - Amount: 1, + Amount: 100000, Conditions: iotago.BasicOutputUnlockConditions{ &iotago.AddressUnlockCondition{ Address: addr, @@ -241,12 +254,12 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { Input: output, }). AddOutput(output). - AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(79, tpkg.Rand32ByteArray())}). + AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(78, tpkg.Rand32ByteArray())}). Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) require.NoError(t, err) - require.ErrorIs(t, createBlockAtSlotWithPayload(t, 100, 80, commitmentInputTooOld, apiProvider), iotago.ErrCommitmentInputTooOld) + require.ErrorIs(t, createBlockAtSlotWithPayload(t, 100, 79, commitmentInputTooOld, apiProvider), iotago.ErrCommitmentInputTooOld) commitmentInputTooRecent, err := builder.NewTransactionBuilder(apiProvider.LatestAPI()). AddInput(&builder.TxInput{ @@ -255,12 +268,12 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { Input: output, }). AddOutput(output). - AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(91, tpkg.Rand32ByteArray())}). + AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(90, tpkg.Rand32ByteArray())}). Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) require.NoError(t, err) - require.ErrorIs(t, createBlockAtSlotWithPayload(t, 100, 90, commitmentInputTooRecent, apiProvider), iotago.ErrCommitmentInputTooRecent) + require.ErrorIs(t, createBlockAtSlotWithPayload(t, 100, 89, commitmentInputTooRecent, apiProvider), iotago.ErrCommitmentInputTooRecent) commitmentInputNewerThanBlockCommitment, err := builder.NewTransactionBuilder(apiProvider.LatestAPI()). AddInput(&builder.TxInput{ @@ -274,7 +287,7 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { require.NoError(t, err) - require.ErrorIs(t, createBlockAtSlotWithPayload(t, 100, 80, commitmentInputNewerThanBlockCommitment, apiProvider), iotago.ErrCommitmentInputNewerThanCommitment) + require.ErrorIs(t, createBlockAtSlotWithPayload(t, 100, 79, commitmentInputNewerThanBlockCommitment, apiProvider), iotago.ErrCommitmentInputNewerThanCommitment) commitmentCorrect, err := builder.NewTransactionBuilder(apiProvider.LatestAPI()). AddInput(&builder.TxInput{ @@ -283,12 +296,12 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { Input: output, }). AddOutput(output). - AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(80, tpkg.Rand32ByteArray())}). + AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(79, tpkg.Rand32ByteArray())}). Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) require.NoError(t, err) - require.NoError(t, createBlockAtSlotWithPayload(t, 100, 90, commitmentCorrect, apiProvider)) + require.NoError(t, createBlockAtSlotWithPayload(t, 100, 89, commitmentCorrect, apiProvider)) commitmentCorrectOldest, err := builder.NewTransactionBuilder(apiProvider.LatestAPI()). AddInput(&builder.TxInput{ @@ -297,12 +310,12 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { Input: output, }). AddOutput(output). - AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(80, tpkg.Rand32ByteArray())}). + AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(79, tpkg.Rand32ByteArray())}). Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) require.NoError(t, err) - require.NoError(t, createBlockAtSlotWithPayload(t, 100, 80, commitmentCorrectOldest, apiProvider)) + require.NoError(t, createBlockAtSlotWithPayload(t, 100, 79, commitmentCorrectOldest, apiProvider)) commitmentCorrectNewest, err := builder.NewTransactionBuilder(apiProvider.LatestAPI()). AddInput(&builder.TxInput{ @@ -311,12 +324,12 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { Input: output, }). AddOutput(output). - AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(90, tpkg.Rand32ByteArray())}). + AddContextInput(&iotago.CommitmentInput{CommitmentID: iotago.NewSlotIdentifier(89, tpkg.Rand32ByteArray())}). Build(iotago.NewInMemoryAddressSigner(iotago.AddressKeys{Address: addr, Keys: ed25519.PrivateKey(keyPair.PrivateKey[:])})) require.NoError(t, err) - require.NoError(t, createBlockAtSlotWithPayload(t, 100, 90, commitmentCorrectNewest, apiProvider)) + require.NoError(t, createBlockAtSlotWithPayload(t, 100, 89, commitmentCorrectNewest, apiProvider)) commitmentCorrectMiddle, err := builder.NewTransactionBuilder(apiProvider.LatestAPI()). AddInput(&builder.TxInput{ @@ -330,7 +343,7 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { require.NoError(t, err) - require.NoError(t, createBlockAtSlotWithPayload(t, 100, 90, commitmentCorrectMiddle, apiProvider)) + require.NoError(t, createBlockAtSlotWithPayload(t, 100, 89, commitmentCorrectMiddle, apiProvider)) } func TestProtocolBlock_DeserializationNotEnoughData(t *testing.T) { From aa81def6bfb56fa90bb59aeaa547560ea2d7d23d Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Mon, 7 Aug 2023 17:12:54 +0200 Subject: [PATCH 07/12] Fix linter errors. --- block_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/block_test.go b/block_test.go index ee72e2fee..e4e7e294d 100644 --- a/block_test.go +++ b/block_test.go @@ -54,6 +54,8 @@ func TestBlock_DeSerialize(t *testing.T) { } func createBlockWithParents(t *testing.T, strongParents, weakParents, shallowLikeParent iotago.BlockIDs, apiProvider *api.EpochBasedProvider) error { + t.Helper() + apiForSlot := apiProvider.LatestAPI() block, err := builder.NewBasicBlockBuilder(apiForSlot). @@ -69,6 +71,8 @@ func createBlockWithParents(t *testing.T, strongParents, weakParents, shallowLik } func createBlockAtSlot(t *testing.T, blockIndex, commitmentIndex iotago.SlotIndex, apiProvider *api.EpochBasedProvider) error { + t.Helper() + apiForSlot := apiProvider.APIForSlot(blockIndex) block, err := builder.NewBasicBlockBuilder(apiForSlot). @@ -96,7 +100,10 @@ func createBlockAtSlotWithVersion(t *testing.T, blockIndex iotago.SlotIndex, ver return lo.Return2(apiForSlot.Encode(block, serix.WithValidation())) } +//nolint:unparam // in the test we always issue at blockIndex=100, but let's keep this flexibility. func createBlockAtSlotWithPayload(t *testing.T, blockIndex, commitmentIndex iotago.SlotIndex, payload iotago.Payload, apiProvider *api.EpochBasedProvider) error { + t.Helper() + apiForSlot := apiProvider.APIForSlot(blockIndex) block, err := builder.NewBasicBlockBuilder(apiForSlot). From d69cb5c0836ac241c90fd19310a1849677095868 Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Tue, 8 Aug 2023 15:56:23 +0200 Subject: [PATCH 08/12] Simplify min/max committable age offsetting by 1. --- block.go | 41 ++++++++++++++++------------------------- block_test.go | 4 ++-- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/block.go b/block.go index 40b485abe..26daebe4c 100644 --- a/block.go +++ b/block.go @@ -317,8 +317,11 @@ func (b *ProtocolBlock) syntacticallyValidate(api API) error { } } - minCommittableAge := api.ProtocolParameters().MinCommittableAge() - maxCommittableAge := api.ProtocolParameters().MaxCommittableAge() + // The "+1" element is there because we're comparing against 'blockIndex' which is in the middle of a slot + // and the oldest possible committed slot is 'minCommittableAge' full slots in the past. + // So we need to subtract 1 to account for the blockIndex slot that is not finished yet. + minCommittableAge := api.ProtocolParameters().MinCommittableAge() + 1 + maxCommittableAge := api.ProtocolParameters().MaxCommittableAge() + 1 commitmentIndex := b.SlotCommitmentID.Index() blockID, err := b.ID(api) if err != nil { @@ -327,20 +330,13 @@ func (b *ProtocolBlock) syntacticallyValidate(api API) error { blockIndex := blockID.Index() // check that commitment is not too recent. - if minCommittableAge > 0 && // don't filter anything for being too recent if minCommittableAge is 0 - commitmentIndex > 0 && // Don't filter commitments to genesis based on being too recent. - // The "+1" element is there because we're comparing against 'blockIndex' which is in the middle of a slot - // and the oldest possible committed slot is 'minCommittableAge' full slots in the past. - // So we need to subtract 1 to account for the blockIndex slot that is not finished yet. - blockIndex < commitmentIndex+minCommittableAge+1 { + 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. - // The "+1" element is there because we're comparing against 'blockIndex' which is in the middle of a slot - // and the oldest possible committed slot is 'maxCommittableAge' full slots in the past. - // So we need to subtract 1 to account for the blockIndex slot that is not finished yet. - if blockIndex > commitmentIndex+maxCommittableAge+1 { + 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) } @@ -434,27 +430,22 @@ func (b *BasicBlock) syntacticallyValidate(api API, protocolBlock *ProtocolBlock } blockIndex := blockID.Index() - minCommittableAge := api.ProtocolParameters().MinCommittableAge() - maxCommittableAge := api.ProtocolParameters().MaxCommittableAge() + // The "+1" element is there because we're comparing against 'blockIndex' which is in the middle of a slot + // and the latest possible committed slot is 'min/maxCommittableAge' full slots in the past. + // So we need to add 1 to an account for the blockIndex slot that is not finished yet. + minCommittableAge := api.ProtocolParameters().MinCommittableAge() + 1 + maxCommittableAge := api.ProtocolParameters().MaxCommittableAge() + 1 tx, _ := b.Payload.(*Transaction) if cInput := tx.CommitmentInput(); cInput != nil { cInputIndex := cInput.CommitmentID.Index() // check that commitment input is not too recent. - if minCommittableAge > 0 && // don't filter anything for being too recent if minCommittableAge is 0 - cInputIndex > 0 && // Don't filter commitments to genesis based on being too recent. - // The "+1" element is there because we're comparing against 'blockIndex' which is in the middle of a slot - // and the latest possible committed slot is 'minCommittableAge' full slots in the past. - // So we need to subtract 1 to account for the blockIndex slot that is not finished yet. - blockIndex < cInputIndex+minCommittableAge+1 { // filter commitments to future slots. - + 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. - // The "+1" element is there because we're comparing against 'blockIndex' which is in the middle of a slot - // and the oldest possible committed slot is 'maxCommittableAge' full slots in the past. - // So we need to subtract 1 to account for the blockIndex slot that is not finished yet. - if blockIndex > cInputIndex+maxCommittableAge+1 { + 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) } diff --git a/block_test.go b/block_test.go index e4e7e294d..71d8a3108 100644 --- a/block_test.go +++ b/block_test.go @@ -63,7 +63,7 @@ func createBlockWithParents(t *testing.T, strongParents, weakParents, shallowLik WeakParents(weakParents). ShallowLikeParents(shallowLikeParent). IssuingTime(time.Now()). - SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), apiForSlot.TimeProvider().SlotFromTime(time.Now())-1-apiForSlot.ProtocolParameters().MinCommittableAge(), iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). + SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), apiForSlot.TimeProvider().SlotFromTime(time.Now())-apiForSlot.ProtocolParameters().MinCommittableAge()-1, iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). Build() require.NoError(t, err) @@ -93,7 +93,7 @@ func createBlockAtSlotWithVersion(t *testing.T, blockIndex iotago.SlotIndex, ver ProtocolVersion(version). StrongParents(iotago.BlockIDs{iotago.BlockID{}}). IssuingTime(apiForSlot.TimeProvider().SlotStartTime(blockIndex)). - SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), blockIndex-1-apiForSlot.ProtocolParameters().MinCommittableAge(), iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). + SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), blockIndex-apiForSlot.ProtocolParameters().MinCommittableAge()-1, iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). Build() require.NoError(t, err) From 532eb2a0e89ad07c87cd3b238099350569006d69 Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:56:24 +0200 Subject: [PATCH 09/12] Update max/min commitable age to include current non-finished slot. --- vm/stardust/stvf_test.go | 8 ++++---- vm/stardust/vm_stardust.go | 2 +- vm/vm.go | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vm/stardust/stvf_test.go b/vm/stardust/stvf_test.go index 6dd531b47..376232f31 100644 --- a/vm/stardust/stvf_test.go +++ b/vm/stardust/stvf_test.go @@ -62,7 +62,7 @@ func TestAccountOutput_ValidateStateTransition(t *testing.T) { exampleBlockIssuerFeature := &iotago.BlockIssuerFeature{ BlockIssuerKeys: []ed25519.PublicKey{tpkg.Rand32ByteArray()}, - ExpirySlot: currentSlot + tpkg.TestAPI.ProtocolParameters().MaxCommittableAge(), + ExpirySlot: currentSlot + tpkg.TestAPI.ProtocolParameters().MaxCommittableAge() + 1, } exampleBIC := map[iotago.AccountID]iotago.BlockIssuanceCredits{ @@ -2509,10 +2509,10 @@ func TestDelegationOutput_ValidateStateTransition(t *testing.T) { currentEpoch := iotago.EpochIndex(20) epochStartSlot := tpkg.TestAPI.TimeProvider().EpochStart(currentEpoch) epochEndSlot := tpkg.TestAPI.TimeProvider().EpochEnd(currentEpoch) - minCommittableAge := tpkg.TestAPI.ProtocolParameters().MinCommittableAge() - maxCommittableAge := tpkg.TestAPI.ProtocolParameters().MaxCommittableAge() + minCommittableAge := tpkg.TestAPI.ProtocolParameters().MinCommittableAge() + 1 + maxCommittableAge := tpkg.TestAPI.ProtocolParameters().MaxCommittableAge() + 1 - // Commitment indices that will always end up being in current epoch, no matter if + // Commitment indices that will always end up being in the current epoch no matter if // future or past bounded. epochStartCommitmentIndex := epochStartSlot - minCommittableAge epochEndCommitmentIndex := epochEndSlot - maxCommittableAge diff --git a/vm/stardust/vm_stardust.go b/vm/stardust/vm_stardust.go index 881bab735..dc375c95c 100644 --- a/vm/stardust/vm_stardust.go +++ b/vm/stardust/vm_stardust.go @@ -335,7 +335,7 @@ func accountBlockIssuerSTVF(input *vm.ChainOutputWithCreationTime, next *iotago. manaOut -= vmParams.WorkingSet.Tx.Essence.Allotments.Get(current.AccountID) // subtract AccountOutLocked - we only consider basic and NFT outputs because only these output types can include a timelock and address unlock condition. - minManalockedSlotIndex := pastBoundedSlotIndex + vmParams.API.ProtocolParameters().MaxCommittableAge() + minManalockedSlotIndex := pastBoundedSlotIndex + vmParams.API.ProtocolParameters().MaxCommittableAge() + 1 for _, output := range vmParams.WorkingSet.OutputsByType[iotago.OutputBasic] { basicOutput, is := output.(*iotago.BasicOutput) if !is { diff --git a/vm/vm.go b/vm/vm.go index 9937e178e..74a0e3e91 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -184,7 +184,7 @@ func TotalManaOut(outputs iotago.Outputs[iotago.TxEssenceOutput], allotments iot // That means no commitment input can be chosen such that the index lies behind the slot index of the block, // hence the past is bounded. func (params *Params) PastBoundedSlotIndex(commitmentInputSlot iotago.SlotIndex) iotago.SlotIndex { - return commitmentInputSlot + params.API.ProtocolParameters().MaxCommittableAge() + return commitmentInputSlot + params.API.ProtocolParameters().MaxCommittableAge() + 1 } // FutureBoundedSlotIndex calculates the future bounded slot for the given slot. @@ -193,7 +193,7 @@ func (params *Params) PastBoundedSlotIndex(commitmentInputSlot iotago.SlotIndex) // That means no commitment input can be chosen such that the index lies ahead of the slot index of the block, // hence the future is bounded. func (params *Params) FutureBoundedSlotIndex(commitmentInputSlot iotago.SlotIndex) iotago.SlotIndex { - return commitmentInputSlot + params.API.ProtocolParameters().MinCommittableAge() + return commitmentInputSlot + params.API.ProtocolParameters().MinCommittableAge() + 1 } // RunVMFuncs runs the given ExecFunc(s) in serial order. From 20d1b7eb01ecb57cf053da68978fdbb387210f42 Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Wed, 9 Aug 2023 13:30:42 +0200 Subject: [PATCH 10/12] Adjust and align the definition of min/max committable age. --- api_protocol_parameters.go | 2 ++ block.go | 8 ++++---- block_test.go | 4 ++-- vm/stardust/stvf_test.go | 6 +++--- vm/stardust/vm_stardust.go | 2 +- vm/vm.go | 4 ++-- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/api_protocol_parameters.go b/api_protocol_parameters.go index b2ebe104c..12ffc5cc1 100644 --- a/api_protocol_parameters.go +++ b/api_protocol_parameters.go @@ -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. diff --git a/block.go b/block.go index 26daebe4c..1048b57cd 100644 --- a/block.go +++ b/block.go @@ -320,8 +320,8 @@ func (b *ProtocolBlock) syntacticallyValidate(api API) error { // The "+1" element is there because we're comparing against 'blockIndex' which is in the middle of a slot // and the oldest possible committed slot is 'minCommittableAge' full slots in the past. // So we need to subtract 1 to account for the blockIndex slot that is not finished yet. - minCommittableAge := api.ProtocolParameters().MinCommittableAge() + 1 - maxCommittableAge := api.ProtocolParameters().MaxCommittableAge() + 1 + minCommittableAge := api.ProtocolParameters().MinCommittableAge() + maxCommittableAge := api.ProtocolParameters().MaxCommittableAge() commitmentIndex := b.SlotCommitmentID.Index() blockID, err := b.ID(api) if err != nil { @@ -433,8 +433,8 @@ func (b *BasicBlock) syntacticallyValidate(api API, protocolBlock *ProtocolBlock // The "+1" element is there because we're comparing against 'blockIndex' which is in the middle of a slot // and the latest possible committed slot is 'min/maxCommittableAge' full slots in the past. // So we need to add 1 to an account for the blockIndex slot that is not finished yet. - minCommittableAge := api.ProtocolParameters().MinCommittableAge() + 1 - maxCommittableAge := api.ProtocolParameters().MaxCommittableAge() + 1 + minCommittableAge := api.ProtocolParameters().MinCommittableAge() + maxCommittableAge := api.ProtocolParameters().MaxCommittableAge() tx, _ := b.Payload.(*Transaction) if cInput := tx.CommitmentInput(); cInput != nil { diff --git a/block_test.go b/block_test.go index 71d8a3108..a27d41964 100644 --- a/block_test.go +++ b/block_test.go @@ -63,7 +63,7 @@ func createBlockWithParents(t *testing.T, strongParents, weakParents, shallowLik WeakParents(weakParents). ShallowLikeParents(shallowLikeParent). IssuingTime(time.Now()). - SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), apiForSlot.TimeProvider().SlotFromTime(time.Now())-apiForSlot.ProtocolParameters().MinCommittableAge()-1, iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). + SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), apiForSlot.TimeProvider().SlotFromTime(time.Now())-apiForSlot.ProtocolParameters().MinCommittableAge(), iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). Build() require.NoError(t, err) @@ -93,7 +93,7 @@ func createBlockAtSlotWithVersion(t *testing.T, blockIndex iotago.SlotIndex, ver ProtocolVersion(version). StrongParents(iotago.BlockIDs{iotago.BlockID{}}). IssuingTime(apiForSlot.TimeProvider().SlotStartTime(blockIndex)). - SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), blockIndex-apiForSlot.ProtocolParameters().MinCommittableAge()-1, iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). + SlotCommitmentID(iotago.NewCommitment(apiForSlot.Version(), blockIndex-apiForSlot.ProtocolParameters().MinCommittableAge(), iotago.CommitmentID{}, iotago.Identifier{}, 0).MustID()). Build() require.NoError(t, err) diff --git a/vm/stardust/stvf_test.go b/vm/stardust/stvf_test.go index 376232f31..64f7bc98e 100644 --- a/vm/stardust/stvf_test.go +++ b/vm/stardust/stvf_test.go @@ -62,7 +62,7 @@ func TestAccountOutput_ValidateStateTransition(t *testing.T) { exampleBlockIssuerFeature := &iotago.BlockIssuerFeature{ BlockIssuerKeys: []ed25519.PublicKey{tpkg.Rand32ByteArray()}, - ExpirySlot: currentSlot + tpkg.TestAPI.ProtocolParameters().MaxCommittableAge() + 1, + ExpirySlot: currentSlot + tpkg.TestAPI.ProtocolParameters().MaxCommittableAge(), } exampleBIC := map[iotago.AccountID]iotago.BlockIssuanceCredits{ @@ -2509,8 +2509,8 @@ func TestDelegationOutput_ValidateStateTransition(t *testing.T) { currentEpoch := iotago.EpochIndex(20) epochStartSlot := tpkg.TestAPI.TimeProvider().EpochStart(currentEpoch) epochEndSlot := tpkg.TestAPI.TimeProvider().EpochEnd(currentEpoch) - minCommittableAge := tpkg.TestAPI.ProtocolParameters().MinCommittableAge() + 1 - maxCommittableAge := tpkg.TestAPI.ProtocolParameters().MaxCommittableAge() + 1 + minCommittableAge := tpkg.TestAPI.ProtocolParameters().MinCommittableAge() + maxCommittableAge := tpkg.TestAPI.ProtocolParameters().MaxCommittableAge() // Commitment indices that will always end up being in the current epoch no matter if // future or past bounded. diff --git a/vm/stardust/vm_stardust.go b/vm/stardust/vm_stardust.go index dc375c95c..881bab735 100644 --- a/vm/stardust/vm_stardust.go +++ b/vm/stardust/vm_stardust.go @@ -335,7 +335,7 @@ func accountBlockIssuerSTVF(input *vm.ChainOutputWithCreationTime, next *iotago. manaOut -= vmParams.WorkingSet.Tx.Essence.Allotments.Get(current.AccountID) // subtract AccountOutLocked - we only consider basic and NFT outputs because only these output types can include a timelock and address unlock condition. - minManalockedSlotIndex := pastBoundedSlotIndex + vmParams.API.ProtocolParameters().MaxCommittableAge() + 1 + minManalockedSlotIndex := pastBoundedSlotIndex + vmParams.API.ProtocolParameters().MaxCommittableAge() for _, output := range vmParams.WorkingSet.OutputsByType[iotago.OutputBasic] { basicOutput, is := output.(*iotago.BasicOutput) if !is { diff --git a/vm/vm.go b/vm/vm.go index 74a0e3e91..9937e178e 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -184,7 +184,7 @@ func TotalManaOut(outputs iotago.Outputs[iotago.TxEssenceOutput], allotments iot // That means no commitment input can be chosen such that the index lies behind the slot index of the block, // hence the past is bounded. func (params *Params) PastBoundedSlotIndex(commitmentInputSlot iotago.SlotIndex) iotago.SlotIndex { - return commitmentInputSlot + params.API.ProtocolParameters().MaxCommittableAge() + 1 + return commitmentInputSlot + params.API.ProtocolParameters().MaxCommittableAge() } // FutureBoundedSlotIndex calculates the future bounded slot for the given slot. @@ -193,7 +193,7 @@ func (params *Params) PastBoundedSlotIndex(commitmentInputSlot iotago.SlotIndex) // That means no commitment input can be chosen such that the index lies ahead of the slot index of the block, // hence the future is bounded. func (params *Params) FutureBoundedSlotIndex(commitmentInputSlot iotago.SlotIndex) iotago.SlotIndex { - return commitmentInputSlot + params.API.ProtocolParameters().MinCommittableAge() + 1 + return commitmentInputSlot + params.API.ProtocolParameters().MinCommittableAge() } // RunVMFuncs runs the given ExecFunc(s) in serial order. From ae626cfbe806152416d2a4089aa7f853b03ece0f Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Wed, 9 Aug 2023 14:44:55 +0200 Subject: [PATCH 11/12] Fix unit tests. --- block_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/block_test.go b/block_test.go index 482e2bb48..682647cd6 100644 --- a/block_test.go +++ b/block_test.go @@ -160,7 +160,7 @@ func TestProtocolBlock_Commitments(t *testing.T) { apiProvider.AddProtocolParametersAtEpoch( iotago.NewV3ProtocolParameters( iotago.WithTimeProviderOptions(time.Now().Add(-20*time.Minute).Unix(), 10, 13), - iotago.WithLivenessOptions(3, 10, 20, 4), + iotago.WithLivenessOptions(3, 11, 21, 4), ), 0) require.ErrorIs(t, createBlockAtSlot(t, 100, 78, apiProvider), iotago.ErrCommitmentTooOld) @@ -180,7 +180,7 @@ func TestProtocolBlock_Commitments1(t *testing.T) { apiProvider.AddProtocolParametersAtEpoch( iotago.NewV3ProtocolParameters( iotago.WithTimeProviderOptions(time.Now().Add(-20*time.Minute).Unix(), 10, 13), - iotago.WithLivenessOptions(3, 6, 20, 4), + iotago.WithLivenessOptions(3, 7, 21, 4), ), 0) require.ErrorIs(t, createBlockAtSlot(t, 10, 4, apiProvider), iotago.ErrCommitmentTooRecent) @@ -252,7 +252,7 @@ func TestProtocolBlock_TransactionCommitmentInput(t *testing.T) { apiProvider.AddProtocolParametersAtEpoch( iotago.NewV3ProtocolParameters( iotago.WithTimeProviderOptions(time.Now().Add(-20*time.Minute).Unix(), 10, 13), - iotago.WithLivenessOptions(3, 10, 20, 4), + iotago.WithLivenessOptions(3, 11, 21, 4), ), 0) commitmentInputTooOld, err := builder.NewTransactionBuilder(apiProvider.LatestAPI()). From 8ee005aa728df4e7cb59beddb755967a558b48f1 Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Thu, 10 Aug 2023 08:02:10 +0200 Subject: [PATCH 12/12] Fix comments that described old definition of min/maxCommittableAge --- block.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/block.go b/block.go index 1048b57cd..7468a81fe 100644 --- a/block.go +++ b/block.go @@ -317,9 +317,6 @@ func (b *ProtocolBlock) syntacticallyValidate(api API) error { } } - // The "+1" element is there because we're comparing against 'blockIndex' which is in the middle of a slot - // and the oldest possible committed slot is 'minCommittableAge' full slots in the past. - // So we need to subtract 1 to account for the blockIndex slot that is not finished yet. minCommittableAge := api.ProtocolParameters().MinCommittableAge() maxCommittableAge := api.ProtocolParameters().MaxCommittableAge() commitmentIndex := b.SlotCommitmentID.Index() @@ -430,9 +427,6 @@ func (b *BasicBlock) syntacticallyValidate(api API, protocolBlock *ProtocolBlock } blockIndex := blockID.Index() - // The "+1" element is there because we're comparing against 'blockIndex' which is in the middle of a slot - // and the latest possible committed slot is 'min/maxCommittableAge' full slots in the past. - // So we need to add 1 to an account for the blockIndex slot that is not finished yet. minCommittableAge := api.ProtocolParameters().MinCommittableAge() maxCommittableAge := api.ProtocolParameters().MaxCommittableAge()