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

Make some Commitment Input checks syntactical #702

Merged
merged 4 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions feat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ func TestBlockIssuerFeatureSyntacticValidation(t *testing.T) {
bik3,
}),
}
t.TransactionEssence.ContextInputs = append(t.TransactionEssence.ContextInputs, tpkg.RandCommitmentInput())
},
),
Target: &iotago.SignedTransaction{},
Expand All @@ -442,6 +443,7 @@ func TestBlockIssuerFeatureSyntacticValidation(t *testing.T) {
bik3,
}),
}
t.TransactionEssence.ContextInputs = append(t.TransactionEssence.ContextInputs, tpkg.RandCommitmentInput())
}),
Target: &iotago.SignedTransaction{},
SeriErr: iotago.ErrArrayValidationOrderViolatesLexicalOrder,
Expand All @@ -458,6 +460,7 @@ func TestBlockIssuerFeatureSyntacticValidation(t *testing.T) {
bik2,
}),
}
t.TransactionEssence.ContextInputs = append(t.TransactionEssence.ContextInputs, tpkg.RandCommitmentInput())
}),
Target: &iotago.SignedTransaction{},
SeriErr: iotago.ErrArrayValidationViolatesUniqueness,
Expand All @@ -469,6 +472,7 @@ func TestBlockIssuerFeatureSyntacticValidation(t *testing.T) {
t.Outputs = iotago.TxEssenceOutputs{
accountWithKeys(iotago.BlockIssuerKeys{}),
}
t.TransactionEssence.ContextInputs = append(t.TransactionEssence.ContextInputs, tpkg.RandCommitmentInput())
}),
Target: &iotago.SignedTransaction{},
SeriErr: serializer.ErrArrayValidationMinElementsNotReached,
Expand All @@ -480,6 +484,7 @@ func TestBlockIssuerFeatureSyntacticValidation(t *testing.T) {
t.Outputs = iotago.TxEssenceOutputs{
accountWithKeys(tpkg.RandBlockIssuerKeys(iotago.MaxBlockIssuerKeysCount + 1)),
}
t.TransactionEssence.ContextInputs = append(t.TransactionEssence.ContextInputs, tpkg.RandCommitmentInput())
}),
Target: &iotago.SignedTransaction{},
SeriErr: serializer.ErrArrayValidationMaxElementsExceeded,
Expand Down
24 changes: 24 additions & 0 deletions output.go
Original file line number Diff line number Diff line change
Expand Up @@ -886,3 +886,27 @@ func OutputsSyntacticalMetadataFeatureMaxSize() ElementValidationFunc[Output] {
return nil
}
}

// Checks that a Commitment Input is present for
// - Accounts with a Staking Feature.
// - Accounts with a Block Issuer Feature.
// - Delegation Outputs.
func OutputsSyntacticalCommitmentInput(hasCommitmentInput bool) ElementValidationFunc[Output] {
return func(index int, output Output) error {
hasStakingFeature := output.FeatureSet().Staking() != nil
if hasStakingFeature && !hasCommitmentInput {
return ierrors.Wrapf(ErrStakingCommitmentInputMissing, "output %d", index)
}

hasBlockIssuerFeature := output.FeatureSet().BlockIssuer() != nil
if hasBlockIssuerFeature && !hasCommitmentInput {
return ierrors.Wrapf(ErrBlockIssuerCommitmentInputMissing, "output %d", index)
}

if output.Type() == OutputDelegation && !hasCommitmentInput {
return ierrors.Wrapf(ErrDelegationCommitmentInputMissing, "output %d", index)
}

return nil
}
}
2 changes: 2 additions & 0 deletions transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ func (t *Transaction) SyntacticallyValidate(api API) error {
}

var maxManaValue Mana = (1 << protoParams.ManaParameters().BitsCount) - 1
hasCommitmentInput := t.CommitmentInput() != nil

return SyntacticallyValidateOutputs(t.Outputs,
OutputsSyntacticalUnlockConditionLexicalOrderAndUniqueness(),
Expand All @@ -276,6 +277,7 @@ func (t *Transaction) SyntacticallyValidate(api API) error {
OutputsSyntacticalDelegation(),
OutputsSyntacticalAddressRestrictions(),
OutputsSyntacticalImplicitAccountCreationAddress(),
OutputsSyntacticalCommitmentInput(hasCommitmentInput),
)
}

Expand Down
96 changes: 96 additions & 0 deletions transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1260,3 +1260,99 @@ func TestTransactionIDsLexicalOrderAndUniqueness(t *testing.T) {
t.Run(tt.Name, tt.Run)
}
}

func TestCommitmentInputSyntacticalValidation(t *testing.T) {
accountWithFeatures := func(feats iotago.AccountOutputFeatures) *iotago.AccountOutput {
return &iotago.AccountOutput{
Amount: 100_000_000,
UnlockConditions: iotago.AccountOutputUnlockConditions{
&iotago.AddressUnlockCondition{
Address: tpkg.RandAccountAddress(),
},
},
ImmutableFeatures: iotago.AccountOutputImmFeatures{},
Features: feats,
}
}

tests := []*frameworks.DeSerializeTest{
// fail - BlockIssuerFeature on output side without Commitment Input
{
Name: "fail - BlockIssuerFeature on output side without Commitment Input",
Source: tpkg.RandSignedTransaction(tpkg.ZeroCostTestAPI, func(t *iotago.Transaction) {
t.Outputs = iotago.TxEssenceOutputs{
accountWithFeatures(
iotago.AccountOutputFeatures{
&iotago.BlockIssuerFeature{
ExpirySlot: 100,
BlockIssuerKeys: tpkg.RandBlockIssuerKeys(3),
},
},
),
}
// Make sure there are no Context Inputs added by the rand function for this test.
t.TransactionEssence.ContextInputs = nil
}),
Target: &iotago.SignedTransaction{},
SeriErr: iotago.ErrBlockIssuerCommitmentInputMissing,
DeSeriErr: iotago.ErrBlockIssuerCommitmentInputMissing,
},
// fail - StakingFeature on output side without Commitment Input
{
Name: "fail - StakingFeature on output side without Commitment Input",
Source: tpkg.RandSignedTransaction(tpkg.ZeroCostTestAPI, func(t *iotago.Transaction) {
t.Outputs = iotago.TxEssenceOutputs{
accountWithFeatures(
iotago.AccountOutputFeatures{
&iotago.BlockIssuerFeature{
ExpirySlot: 100,
BlockIssuerKeys: tpkg.RandBlockIssuerKeys(3),
},
&iotago.StakingFeature{
StakedAmount: 1,
FixedCost: 1,
StartEpoch: 10,
EndEpoch: 12,
},
},
),
}
// Make sure there are no Context Inputs added by the rand function for this test.
t.TransactionEssence.ContextInputs = nil
}),
Target: &iotago.SignedTransaction{},
SeriErr: iotago.ErrStakingCommitmentInputMissing,
DeSeriErr: iotago.ErrStakingCommitmentInputMissing,
},
// fail - Delegation Output on output side without Commitment Input
{
Name: "fail - Delegation Output on output side without Commitment Input",
Source: tpkg.RandSignedTransaction(tpkg.ZeroCostTestAPI, func(t *iotago.Transaction) {
t.Outputs = iotago.TxEssenceOutputs{
&iotago.DelegationOutput{
Amount: 10,
DelegatedAmount: 10,
DelegationID: tpkg.RandDelegationID(),
ValidatorAddress: tpkg.RandAccountAddress(),
StartEpoch: 10,
EndEpoch: 12,
UnlockConditions: iotago.DelegationOutputUnlockConditions{
&iotago.AddressUnlockCondition{
Address: tpkg.RandEd25519Address(),
},
},
},
}
// Make sure there are no Context Inputs added by the rand function for this test.
t.TransactionEssence.ContextInputs = nil
}),
Target: &iotago.SignedTransaction{},
SeriErr: iotago.ErrDelegationCommitmentInputMissing,
DeSeriErr: iotago.ErrDelegationCommitmentInputMissing,
},
}

for _, tt := range tests {
t.Run(tt.Name, tt.Run)
}
}
5 changes: 2 additions & 3 deletions vm/nova/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ func accountGenesisValid(vmParams *vm.Params, next *iotago.AccountOutput, accoun

if nextBlockIssuerFeat := next.FeatureSet().BlockIssuer(); nextBlockIssuerFeat != nil {
if vmParams.WorkingSet.Commitment == nil {
return ierrors.Join(iotago.ErrInvalidBlockIssuerTransition, iotago.ErrBlockIssuerCommitmentInputMissing)
panic("commitment input should be present for block issuer features on the output side which should be validated syntactically")
}

pastBoundedSlot := vmParams.PastBoundedSlotIndex(vmParams.WorkingSet.Commitment.Slot)
Expand Down Expand Up @@ -485,10 +485,9 @@ func accountStakingSTVF(vmParams *vm.Params, current *iotago.AccountOutput, next
// or one which was effectively removed and added within the same transaction.
// This is allowed as long as the epoch range of the old and new feature are disjoint.
func accountStakingGenesisValidation(vmParams *vm.Params, next *iotago.AccountOutput, stakingFeat *iotago.StakingFeature) error {
// It should already never be nil here, but for 100% safety, we'll check again.
commitment := vmParams.WorkingSet.Commitment
if commitment == nil {
return iotago.ErrStakingCommitmentInputMissing
panic("commitment input should be present for staking features on the output side which should be validated syntactically")
}

pastBoundedSlot := vmParams.PastBoundedSlotIndex(commitment.Slot)
Expand Down
35 changes: 24 additions & 11 deletions vm/nova/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7526,7 +7526,7 @@ func TestTxSemanticImplicitAccountCreationAndTransition(t *testing.T) {
name string
inputs []TestInput
keys []iotago.AddressKeys
resolvedCommitmentInput iotago.Commitment
resolvedCommitmentInput *iotago.Commitment
resolvedBICInputSet vm.BlockIssuanceCreditInputSet
outputs []iotago.Output
wantErr error
Expand Down Expand Up @@ -7686,7 +7686,7 @@ func TestTxSemanticImplicitAccountCreationAndTransition(t *testing.T) {
resolvedBICInputSet: vm.BlockIssuanceCreditInputSet{
accountID1: iotago.BlockIssuanceCredits(0),
},
resolvedCommitmentInput: iotago.Commitment{
resolvedCommitmentInput: &iotago.Commitment{
Slot: commitmentSlot,
},
outputs: []iotago.Output{
Expand Down Expand Up @@ -7735,7 +7735,7 @@ func TestTxSemanticImplicitAccountCreationAndTransition(t *testing.T) {
resolvedBICInputSet: vm.BlockIssuanceCreditInputSet{
accountID1: iotago.BlockIssuanceCredits(0),
},
resolvedCommitmentInput: iotago.Commitment{
resolvedCommitmentInput: &iotago.Commitment{
Slot: commitmentSlot,
},
outputs: []iotago.Output{
Expand Down Expand Up @@ -7792,7 +7792,7 @@ func TestTxSemanticImplicitAccountCreationAndTransition(t *testing.T) {
resolvedBICInputSet: vm.BlockIssuanceCreditInputSet{
accountID1: iotago.BlockIssuanceCredits(0),
},
resolvedCommitmentInput: iotago.Commitment{
resolvedCommitmentInput: &iotago.Commitment{
Slot: commitmentSlot,
},
outputs: []iotago.Output{
Expand Down Expand Up @@ -7829,7 +7829,7 @@ func TestTxSemanticImplicitAccountCreationAndTransition(t *testing.T) {
resolvedBICInputSet: vm.BlockIssuanceCreditInputSet{
accountID1: iotago.BlockIssuanceCredits(0),
},
resolvedCommitmentInput: iotago.Commitment{
resolvedCommitmentInput: &iotago.Commitment{
Slot: commitmentSlot,
},
outputs: []iotago.Output{
Expand Down Expand Up @@ -7862,7 +7862,7 @@ func TestTxSemanticImplicitAccountCreationAndTransition(t *testing.T) {
resolvedBICInputSet: vm.BlockIssuanceCreditInputSet{
accountID1: iotago.BlockIssuanceCredits(0),
},
resolvedCommitmentInput: iotago.Commitment{
resolvedCommitmentInput: &iotago.Commitment{
Slot: commitmentSlot,
},
outputs: []iotago.Output{
Expand Down Expand Up @@ -7909,7 +7909,7 @@ func TestTxSemanticImplicitAccountCreationAndTransition(t *testing.T) {
resolvedBICInputSet: vm.BlockIssuanceCreditInputSet{
accountID1: iotago.BlockIssuanceCredits(0),
},
resolvedCommitmentInput: iotago.Commitment{
resolvedCommitmentInput: &iotago.Commitment{
Slot: commitmentSlot,
},
outputs: []iotago.Output{
Expand Down Expand Up @@ -7965,7 +7965,7 @@ func TestTxSemanticImplicitAccountCreationAndTransition(t *testing.T) {
resolvedBICInputSet: vm.BlockIssuanceCreditInputSet{
accountID1: iotago.BlockIssuanceCredits(0),
},
resolvedCommitmentInput: iotago.Commitment{
resolvedCommitmentInput: &iotago.Commitment{
Slot: commitmentSlot,
},
outputs: []iotago.Output{
Expand Down Expand Up @@ -8022,7 +8022,7 @@ func TestTxSemanticImplicitAccountCreationAndTransition(t *testing.T) {
accountID1: iotago.BlockIssuanceCredits(0),
accountID2: iotago.BlockIssuanceCredits(0),
},
resolvedCommitmentInput: iotago.Commitment{
resolvedCommitmentInput: &iotago.Commitment{
Slot: commitmentSlot,
},
outputs: []iotago.Output{
Expand Down Expand Up @@ -8084,7 +8084,7 @@ func TestTxSemanticImplicitAccountCreationAndTransition(t *testing.T) {
resolvedBICInputSet: vm.BlockIssuanceCreditInputSet{
accountID1: iotago.BlockIssuanceCredits(0),
},
resolvedCommitmentInput: iotago.Commitment{
resolvedCommitmentInput: &iotago.Commitment{
Slot: commitmentSlot,
},
outputs: []iotago.Output{
Expand Down Expand Up @@ -8131,6 +8131,19 @@ func TestTxSemanticImplicitAccountCreationAndTransition(t *testing.T) {
iotago.TransactionCapabilitiesBitMaskWithCapabilities(iotago.WithTransactionCanBurnNativeTokens(true)),
)

// Add the BIC and Commitment Inputs to the TX builder since they are required syntactically.
// Note that this has no effect on the actual test.
for accountID := range tests[idx].resolvedBICInputSet {
txBuilder.AddBlockIssuanceCreditInput(&iotago.BlockIssuanceCreditInput{
AccountID: accountID,
})
}
if tests[idx].resolvedCommitmentInput != nil {
txBuilder.AddCommitmentInput(&iotago.CommitmentInput{
CommitmentID: tests[idx].resolvedCommitmentInput.MustID(),
})
}

for _, input := range tests[idx].inputs {
txBuilder.AddInput(&builder.TxInput{
UnlockTarget: input.unlockTarget,
Expand All @@ -8148,7 +8161,7 @@ func TestTxSemanticImplicitAccountCreationAndTransition(t *testing.T) {
tx := lo.PanicOnErr(txBuilder.Build())

resolvedInputs.BlockIssuanceCreditInputSet = tests[idx].resolvedBICInputSet
resolvedInputs.CommitmentInput = &tests[idx].resolvedCommitmentInput
resolvedInputs.CommitmentInput = tests[idx].resolvedCommitmentInput

t.Run(tt.name, func(t *testing.T) {
var err error
Expand Down
Loading