Skip to content

Commit

Permalink
Merge pull request #474 from iotaledger/feat/resolvable-inputs
Browse files Browse the repository at this point in the history
Unify and flatten Input interfaces and types
  • Loading branch information
karimodm authored Aug 10, 2023
2 parents 78abfd8 + 5e09959 commit 3fec1b9
Show file tree
Hide file tree
Showing 16 changed files with 120 additions and 101 deletions.
6 changes: 3 additions & 3 deletions api_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,13 +446,13 @@ func V3API(protoParams ProtocolParameters) API {
must(api.RegisterTypeSettings(TransactionEssence{}, serix.TypeSettings{}.WithObjectType(TransactionEssenceNormal)))

must(api.RegisterTypeSettings(CommitmentInput{},
serix.TypeSettings{}.WithObjectType(uint8(ContextInputCommitment))),
serix.TypeSettings{}.WithObjectType(uint8(InputCommitment))),
)
must(api.RegisterTypeSettings(BlockIssuanceCreditInput{},
serix.TypeSettings{}.WithObjectType(uint8(ContextInputBlockIssuanceCredit))),
serix.TypeSettings{}.WithObjectType(uint8(InputBlockIssuanceCredit))),
)
must(api.RegisterTypeSettings(RewardInput{},
serix.TypeSettings{}.WithObjectType(uint8(ContextInputReward))),
serix.TypeSettings{}.WithObjectType(uint8(InputReward))),
)

must(api.RegisterTypeSettings(TxEssenceContextInputs{},
Expand Down
6 changes: 3 additions & 3 deletions builder/transaction_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (b *TransactionBuilder) AddInput(input *TxInput) *TransactionBuilder {
type TransactionBuilderInputFilter func(outputID iotago.OutputID, input iotago.Output) bool

// AddContextInput adds the given context input to the builder.
func (b *TransactionBuilder) AddContextInput(contextInput iotago.ContextInput) *TransactionBuilder {
func (b *TransactionBuilder) AddContextInput(contextInput iotago.Input) *TransactionBuilder {
b.essence.ContextInputs = append(b.essence.ContextInputs, contextInput)

return b
Expand Down Expand Up @@ -120,7 +120,7 @@ func (b *TransactionBuilder) Build(signer iotago.AddressSigner) (*iotago.Transac
var inputIDs iotago.OutputIDs
for _, input := range b.essence.Inputs {
//nolint:forcetypeassert // we can safely assume that this is an UTXOInput
inputIDs = append(inputIDs, input.(*iotago.UTXOInput).ID())
inputIDs = append(inputIDs, input.(*iotago.UTXOInput).OutputID())
}

inputs := inputIDs.OrderedSet(b.inputs)
Expand All @@ -139,7 +139,7 @@ func (b *TransactionBuilder) Build(signer iotago.AddressSigner) (*iotago.Transac
unlocks := iotago.Unlocks{}
for i, inputRef := range b.essence.Inputs {
//nolint:forcetypeassert // we can safely assume that this is an UTXOInput
addr := b.inputOwner[inputRef.(*iotago.UTXOInput).ID()]
addr := b.inputOwner[inputRef.(*iotago.UTXOInput).OutputID()]
addrKey := addr.Key()

pos, unlocked := unlockPos[addrKey]
Expand Down
16 changes: 8 additions & 8 deletions builder/transaction_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestTransactionBuilder(t *testing.T) {
inputUTXO1 := &iotago.UTXOInput{TransactionID: tpkg.Rand32ByteArray(), TransactionOutputIndex: 0}
input := tpkg.RandBasicOutput(iotago.AddressEd25519)
bdl := builder.NewTransactionBuilder(tpkg.TestAPI).
AddInput(&builder.TxInput{UnlockTarget: inputAddr, InputID: inputUTXO1.ID(), Input: input}).
AddInput(&builder.TxInput{UnlockTarget: inputAddr, InputID: inputUTXO1.OutputID(), Input: input}).
AddOutput(&iotago.BasicOutput{
Amount: 50,
Conditions: iotago.BasicOutputUnlockConditions{
Expand Down Expand Up @@ -84,10 +84,10 @@ func TestTransactionBuilder(t *testing.T) {
)

bdl := builder.NewTransactionBuilder(tpkg.TestAPI).
AddInput(&builder.TxInput{UnlockTarget: inputAddr, InputID: inputID1.ID(), Input: basicOutput}).
AddInput(&builder.TxInput{UnlockTarget: inputAddr, InputID: inputID2.ID(), Input: nftOutput}).
AddInput(&builder.TxInput{UnlockTarget: nftOutput.Chain().ToAddress(), InputID: inputID3.ID(), Input: accountOwnedByNFT}).
AddInput(&builder.TxInput{UnlockTarget: accountOwnedByNFT.Chain().ToAddress(), InputID: inputID4.ID(), Input: basicOwnedByAccount}).
AddInput(&builder.TxInput{UnlockTarget: inputAddr, InputID: inputID1.OutputID(), Input: basicOutput}).
AddInput(&builder.TxInput{UnlockTarget: inputAddr, InputID: inputID2.OutputID(), Input: nftOutput}).
AddInput(&builder.TxInput{UnlockTarget: nftOutput.Chain().ToAddress(), InputID: inputID3.OutputID(), Input: accountOwnedByNFT}).
AddInput(&builder.TxInput{UnlockTarget: accountOwnedByNFT.Chain().ToAddress(), InputID: inputID4.OutputID(), Input: basicOwnedByAccount}).
AddOutput(&iotago.BasicOutput{
Amount: 4000,
Conditions: iotago.BasicOutputUnlockConditions{
Expand All @@ -105,7 +105,7 @@ func TestTransactionBuilder(t *testing.T) {
inputUTXO1 := &iotago.UTXOInput{TransactionID: tpkg.Rand32ByteArray(), TransactionOutputIndex: 0}

bdl := builder.NewTransactionBuilder(tpkg.TestAPI).
AddInput(&builder.TxInput{UnlockTarget: inputAddr, InputID: inputUTXO1.ID(), Input: tpkg.RandBasicOutput(iotago.AddressEd25519)}).
AddInput(&builder.TxInput{UnlockTarget: inputAddr, InputID: inputUTXO1.OutputID(), Input: tpkg.RandBasicOutput(iotago.AddressEd25519)}).
AddOutput(&iotago.BasicOutput{
Amount: 50,
Conditions: iotago.BasicOutputUnlockConditions{
Expand All @@ -124,7 +124,7 @@ func TestTransactionBuilder(t *testing.T) {
inputUTXO1 := &iotago.UTXOInput{TransactionID: tpkg.Rand32ByteArray(), TransactionOutputIndex: 0}

bdl := builder.NewTransactionBuilder(tpkg.TestAPI).
AddInput(&builder.TxInput{UnlockTarget: inputAddr, InputID: inputUTXO1.ID(), Input: tpkg.RandBasicOutput(iotago.AddressEd25519)}).
AddInput(&builder.TxInput{UnlockTarget: inputAddr, InputID: inputUTXO1.OutputID(), Input: tpkg.RandBasicOutput(iotago.AddressEd25519)}).
AddOutput(&iotago.BasicOutput{
Amount: 50,
Conditions: iotago.BasicOutputUnlockConditions{
Expand All @@ -149,7 +149,7 @@ func TestTransactionBuilder(t *testing.T) {
inputUTXO1 := &iotago.UTXOInput{TransactionID: tpkg.Rand32ByteArray(), TransactionOutputIndex: 0}

bdl := builder.NewTransactionBuilder(tpkg.TestAPI).
AddInput(&builder.TxInput{UnlockTarget: inputAddr, InputID: inputUTXO1.ID(), Input: tpkg.RandBasicOutput(iotago.AddressEd25519)}).
AddInput(&builder.TxInput{UnlockTarget: inputAddr, InputID: inputUTXO1.OutputID(), Input: tpkg.RandBasicOutput(iotago.AddressEd25519)}).
AddOutput(&iotago.BasicOutput{
Amount: 50,
Conditions: iotago.BasicOutputUnlockConditions{
Expand Down
9 changes: 9 additions & 0 deletions commitment.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

"github.com/iotaledger/hive.go/ierrors"
"github.com/iotaledger/hive.go/lo"
"github.com/iotaledger/hive.go/serializer/v2"
)

Expand Down Expand Up @@ -49,6 +50,14 @@ func (c *Commitment) ID() (CommitmentID, error) {
return SlotIdentifierRepresentingData(c.Index, data), nil
}

func (c *Commitment) StateID() Identifier {
return IdentifierFromData(lo.PanicOnErr(c.MustID().Bytes()))
}

func (c *Commitment) Type() StateType {
return InputCommitment
}

func (c *Commitment) MustID() CommitmentID {
id, err := c.ID()
if err != nil {
Expand Down
44 changes: 20 additions & 24 deletions input.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,29 @@ import (
"github.com/iotaledger/hive.go/serializer/v2"
)

// InputType defines the type of inputs.
type InputType byte
// StateType defines the type of inputs.
type StateType byte

const (
// InputUTXO is a type of input which references an unspent transaction output.
InputUTXO InputType = iota
InputUTXO StateType = iota
// InputCommitment is a type of input which references a commitment.
InputCommitment
// InputBlockIssuanceCredit is a type of input which references the block issuance credit from a specific account and commitment, the latter being provided by a commitment input.
InputBlockIssuanceCredit
// InputReward is a type of input which references an Account or Delegation Input for which to claim rewards.
InputReward
)

func (inputType InputType) String() string {
func (inputType StateType) String() string {
if int(inputType) >= len(inputNames) {
return fmt.Sprintf("unknown input type: %d", inputType)
}

return inputNames[inputType]
}

var (
inputNames = [InputUTXO + 1]string{"UTXOInput"}
)
var inputNames = [InputUTXO + 1]string{"UTXOInput"}

var (
// ErrRefUTXOIndexInvalid gets returned on invalid UTXO indices.
Expand Down Expand Up @@ -68,23 +72,15 @@ func (in Inputs[T]) WorkScore(workScoreStructure *WorkScoreStructure) (WorkScore
return workScoreBytes, nil
}

// Input references a UTXO.
// Input references a generic input.
type Input interface {
Sizer
ProcessableObject

// Type returns the type of Input.
Type() InputType
}

// IndexedUTXOReferencer is a type of Input which references a UTXO by the transaction ID and output index.
type IndexedUTXOReferencer interface {
Input
StateID() Identifier

// Ref returns the UTXO this Input references.
Ref() OutputID
// Index returns the output index of the UTXO this Input references.
Index() uint16
// Type returns the type of Input.
Type() StateType
}

// InputsSyntacticalValidationFunc which given the index of an input and the input itself, runs syntactical validations and returns an error if any should fail.
Expand All @@ -96,15 +92,15 @@ func InputsSyntacticalUnique() InputsSyntacticalValidationFunc {

return func(index int, input Input) error {
switch castInput := input.(type) {
case IndexedUTXOReferencer:
utxoRef := castInput.Ref()
case *UTXOInput:
utxoRef := castInput.OutputID()
k := string(utxoRef[:])
if j, has := utxoSet[k]; has {
return ierrors.Wrapf(ErrInputUTXORefsNotUnique, "input %d and %d share the same UTXO ref", j, index)
}
utxoSet[k] = index
default:
return ierrors.Wrapf(ErrUnknownInputType, "input %d, tx can only contain IndexedUTXOReferencer", index)
return ierrors.Wrapf(ErrUnknownInputType, "input %d, tx can only contain UTXO inputs", index)
}

return nil
Expand All @@ -115,12 +111,12 @@ func InputsSyntacticalUnique() InputsSyntacticalValidationFunc {
func InputsSyntacticalIndicesWithinBounds() InputsSyntacticalValidationFunc {
return func(index int, input Input) error {
switch castInput := input.(type) {
case IndexedUTXOReferencer:
case *UTXOInput:
if castInput.Index() < RefUTXOIndexMin || castInput.Index() > RefUTXOIndexMax {
return ierrors.Wrapf(ErrRefUTXOIndexInvalid, "input %d", index)
}
default:
return ierrors.Wrapf(ErrUnknownInputType, "input %d, tx can only contain IndexedUTXOReferencer inputs", index)
return ierrors.Wrapf(ErrUnknownInputType, "input %d, tx can only contain UTXInput inputs", index)
}

return nil
Expand Down
8 changes: 6 additions & 2 deletions input_block_issuance_credit.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ type BlockIssuanceCreditInput struct {
AccountID AccountID `serix:"0,mapKey=accountId"`
}

func (b *BlockIssuanceCreditInput) Type() ContextInputType {
return ContextInputBlockIssuanceCredit
func (b *BlockIssuanceCreditInput) StateID() Identifier {
return IdentifierFromData(b.AccountID[:])
}

func (b *BlockIssuanceCreditInput) Type() StateType {
return InputBlockIssuanceCredit
}

func (b *BlockIssuanceCreditInput) Size() int {
Expand Down
8 changes: 6 additions & 2 deletions input_commitment.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ type CommitmentInput struct {
CommitmentID CommitmentID `serix:"0,mapKey=commitmentId"`
}

func (c *CommitmentInput) Type() ContextInputType {
return ContextInputCommitment
func (c *CommitmentInput) StateID() Identifier {
return IdentifierFromData(c.CommitmentID[:])
}

func (c *CommitmentInput) Type() StateType {
return InputCommitment
}

func (c *CommitmentInput) Size() int {
Expand Down
30 changes: 6 additions & 24 deletions input_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,6 @@ import (
// ContextInputType defines the type of context inputs.
type ContextInputType byte

const (
// ContextInputCommitment is a type of input which references a commitment.
ContextInputCommitment ContextInputType = iota
// ContextInputBlockIssuanceCredit is a type of input which references the block issuance credit from a specific account and commitment, the latter being provided by a commitment input.
ContextInputBlockIssuanceCredit
// ContextInputReward is a type of input which references an Account or Delegation Input for which to claim rewards.
ContextInputReward
)

func (inputType ContextInputType) String() string {
if int(inputType) >= len(contextInputNames) {
return fmt.Sprintf("unknown input type: %d", inputType)
Expand All @@ -27,12 +18,10 @@ func (inputType ContextInputType) String() string {
return contextInputNames[inputType]
}

var (
contextInputNames = [ContextInputReward + 1]string{"CommitmentInput", "BlockIssuanceCreditInput", "RewardInput"}
)
var contextInputNames = [InputReward + 1]string{"CommitmentInput", "BlockIssuanceCreditInput", "RewardInput"}

// ContextInputs is a slice of ContextInput.
type ContextInputs[T ContextInput] []T
type ContextInputs[T Input] []T

func (in ContextInputs[T]) WorkScore(workScoreStructure *WorkScoreStructure) (WorkScore, error) {
// LengthPrefixType
Expand Down Expand Up @@ -65,18 +54,9 @@ func (in ContextInputs[T]) Size() int {
return sum
}

// ContextInput provides an additional contextual input for transaction validation.
type ContextInput interface {
Sizer
ProcessableObject

// Type returns the type of ContextInput.
Type() ContextInputType
}

// ContextInputsSyntacticalValidationFunc which given the index of an input and the input itself,
// runs syntactical validations and returns an error if any should fail.
type ContextInputsSyntacticalValidationFunc func(index int, input ContextInput) error
type ContextInputsSyntacticalValidationFunc func(index int, input Input) error

// ContextInputsSyntacticalUnique returns a ContextInputsSyntacticalValidationFunc
// which checks that
Expand All @@ -88,7 +68,7 @@ func ContextInputsSyntacticalUnique() ContextInputsSyntacticalValidationFunc {
bicSet := map[string]int{}
rewardSet := map[uint16]int{}

return func(index int, input ContextInput) error {
return func(index int, input Input) error {
switch castInput := input.(type) {
case *BlockIssuanceCreditInput:
accountID := castInput.AccountID
Expand All @@ -111,6 +91,8 @@ func ContextInputsSyntacticalUnique() ContextInputsSyntacticalValidationFunc {
return ierrors.Wrapf(ErrMultipleInputCommitments, "input %d is the second commitment input", index)
}
hasCommitment = true
case *UTXOInput:
// ignore as we are evaluating context inputs only
default:
return ierrors.Wrapf(ErrUnknownContextInputType, "context input %d, tx can only contain CommitmentInputs, BlockIssuanceCreditInputs or RewardInputs", index)
}
Expand Down
12 changes: 10 additions & 2 deletions input_reward.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package iotago

import (
"encoding/binary"

"github.com/iotaledger/hive.go/serializer/v2"
)

Expand All @@ -9,8 +11,14 @@ type RewardInput struct {
Index uint16 `serix:"0,mapKey=index"`
}

func (r *RewardInput) Type() ContextInputType {
return ContextInputReward
func (r *RewardInput) StateID() Identifier {
buf := make([]byte, 2)
binary.LittleEndian.PutUint16(buf, r.Index)
return IdentifierFromData(buf)
}

func (r *RewardInput) Type() StateType {
return InputReward
}

func (r *RewardInput) Size() int {
Expand Down
12 changes: 6 additions & 6 deletions input_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ func TestInputsSyntacticalUnique(t *testing.T) {
func TestContextInputsSyntacticalUnique(t *testing.T) {
tests := []struct {
name string
inputs iotago.ContextInputs[iotago.ContextInput]
inputs iotago.ContextInputs[iotago.Input]
wantErr error
}{
{
name: "ok",
inputs: iotago.ContextInputs[iotago.ContextInput]{
inputs: iotago.ContextInputs[iotago.Input]{
&iotago.CommitmentInput{
CommitmentID: tpkg.Rand40ByteArray(),
},
Expand All @@ -88,7 +88,7 @@ func TestContextInputsSyntacticalUnique(t *testing.T) {
},
{
name: "fail - multiple commitment inputs",
inputs: iotago.ContextInputs[iotago.ContextInput]{
inputs: iotago.ContextInputs[iotago.Input]{
&iotago.CommitmentInput{
CommitmentID: tpkg.Rand40ByteArray(),
},
Expand All @@ -100,7 +100,7 @@ func TestContextInputsSyntacticalUnique(t *testing.T) {
},
{
name: "fail - block issuance credit inputs not unique",
inputs: iotago.ContextInputs[iotago.ContextInput]{
inputs: iotago.ContextInputs[iotago.Input]{
&iotago.BlockIssuanceCreditInput{
AccountID: [32]byte{},
},
Expand All @@ -112,7 +112,7 @@ func TestContextInputsSyntacticalUnique(t *testing.T) {
},
{
name: "fail - reward input not unique",
inputs: iotago.ContextInputs[iotago.ContextInput]{
inputs: iotago.ContextInputs[iotago.Input]{
&iotago.RewardInput{
Index: 1,
},
Expand All @@ -124,7 +124,7 @@ func TestContextInputsSyntacticalUnique(t *testing.T) {
},
{
name: "fail - reward input references index greater than max inputs count",
inputs: iotago.ContextInputs[iotago.ContextInput]{
inputs: iotago.ContextInputs[iotago.Input]{
&iotago.RewardInput{
Index: 1,
},
Expand Down
Loading

0 comments on commit 3fec1b9

Please sign in to comment.