Skip to content
Snippets Groups Projects
Unverified Commit 16bd8d85 authored by Hans Moog's avatar Hans Moog Committed by GitHub
Browse files

Double spend check (#606)


* Fix: added a check for doublespends

* Fix: added a check for double spends

* Fix: fixed iport cycle

* Feat: replaced error with error variable

* Feat: store tangle in factory

* Update dapps/faucet/packages/faucet.go

Co-authored-by: default avatarWolfgang Welz <welzwo@gmail.com>

* makes send tx web API handler synced

Co-authored-by: default avatarHans Moog <hm@mkjc.net>
Co-authored-by: default avatarWolfgang Welz <welzwo@gmail.com>
Co-authored-by: default avatarLuca Moser <moser.luca@gmail.com>
parent fa88a081
Branches
No related tags found
No related merge requests found
package faucet package faucet
import ( import (
"errors"
"fmt" "fmt"
"sync" "sync"
"time" "time"
...@@ -16,13 +15,6 @@ import ( ...@@ -16,13 +15,6 @@ import (
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet" "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet"
"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message" "github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
"github.com/iotaledger/goshimmer/plugins/issuer" "github.com/iotaledger/goshimmer/plugins/issuer"
"github.com/iotaledger/hive.go/events"
)
var (
// ErrFundingTxNotBookedInTime is returned when a funding transaction didn't get booked
// by this node in the maximum defined await time for it to get booked.
ErrFundingTxNotBookedInTime = errors.New("funding transaction didn't get booked in time")
) )
// New creates a new faucet using the given seed and tokensPerRequest config. // New creates a new faucet using the given seed and tokensPerRequest config.
...@@ -80,7 +72,10 @@ func (f *Faucet) SendFunds(msg *message.Message) (m *message.Message, txID strin ...@@ -80,7 +72,10 @@ func (f *Faucet) SendFunds(msg *message.Message) (m *message.Message, txID strin
} }
// prepare value payload with value factory // prepare value payload with value factory
payload := valuetransfers.ValueObjectFactory().IssueTransaction(tx) payload, err := valuetransfers.ValueObjectFactory().IssueTransaction(tx)
if err != nil {
return nil, "", fmt.Errorf("failed to issue transaction: %w", err)
}
// attach to message layer // attach to message layer
msg, err = issuer.IssuePayload(payload) msg, err = issuer.IssuePayload(payload)
...@@ -91,42 +86,13 @@ func (f *Faucet) SendFunds(msg *message.Message) (m *message.Message, txID strin ...@@ -91,42 +86,13 @@ func (f *Faucet) SendFunds(msg *message.Message) (m *message.Message, txID strin
// block for a certain amount of time until we know that the transaction // block for a certain amount of time until we know that the transaction
// actually got booked by this node itself // actually got booked by this node itself
// TODO: replace with an actual more reactive way // TODO: replace with an actual more reactive way
bookedInTime := f.awaitTransactionBooked(tx.ID(), f.maxTxBookedAwaitTime) if err := valuetransfers.AwaitTransactionToBeBooked(tx.ID(), f.maxTxBookedAwaitTime); err != nil {
if !bookedInTime { return nil, "", fmt.Errorf("%w: tx %s", err, tx.ID().String())
return nil, "", fmt.Errorf("%w: tx %s", ErrFundingTxNotBookedInTime, tx.ID().String())
} }
return msg, tx.ID().String(), nil return msg, tx.ID().String(), nil
} }
// awaitTransactionBooked awaits maxAwait for the given transaction to get booked.
func (f *Faucet) awaitTransactionBooked(txID transaction.ID, maxAwait time.Duration) bool {
booked := make(chan struct{}, 1)
// exit is used to let the caller exit if for whatever
// reason the same transaction gets booked multiple times
exit := make(chan struct{})
defer close(exit)
closure := events.NewClosure(func(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *tangle.CachedTransactionMetadata, decisionPending bool) {
defer cachedTransaction.Release()
defer cachedTransactionMetadata.Release()
if cachedTransaction.Unwrap().ID() != txID {
return
}
select {
case booked <- struct{}{}:
case <-exit:
}
})
valuetransfers.Tangle().Events.TransactionBooked.Attach(closure)
defer valuetransfers.Tangle().Events.TransactionBooked.Detach(closure)
select {
case <-time.After(maxAwait):
return false
case <-booked:
return true
}
}
// collectUTXOsForFunding iterates over the faucet's UTXOs until the token threshold is reached. // collectUTXOsForFunding iterates over the faucet's UTXOs until the token threshold is reached.
// this function also returns the remainder balance for the given outputs. // this function also returns the remainder balance for the given outputs.
func (f *Faucet) collectUTXOsForFunding() (outputIds []transaction.OutputID, addrsIndices map[uint64]struct{}, remainder int64) { func (f *Faucet) collectUTXOsForFunding() (outputIds []transaction.OutputID, addrsIndices map[uint64]struct{}, remainder int64) {
......
package valuetransfers package valuetransfers
import ( import (
"errors"
"os" "os"
"sync" "sync"
"time" "time"
...@@ -10,6 +11,7 @@ import ( ...@@ -10,6 +11,7 @@ import (
valuepayload "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload" valuepayload "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tangle" "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tangle"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tipmanager" "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tipmanager"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message" "github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
messageTangle "github.com/iotaledger/goshimmer/packages/binary/messagelayer/tangle" messageTangle "github.com/iotaledger/goshimmer/packages/binary/messagelayer/tangle"
"github.com/iotaledger/goshimmer/packages/shutdown" "github.com/iotaledger/goshimmer/packages/shutdown"
...@@ -44,6 +46,10 @@ func init() { ...@@ -44,6 +46,10 @@ func init() {
} }
var ( var (
// ErrTransactionWasNotBookedInTime is returned if a transaction did not get booked
// within the defined await time.
ErrTransactionWasNotBookedInTime = errors.New("transaction could not be booked in time")
// app is the "plugin" instance of the value-transfers application. // app is the "plugin" instance of the value-transfers application.
app *node.Plugin app *node.Plugin
appOnce sync.Once appOnce sync.Once
...@@ -217,7 +223,35 @@ func TipManager() *tipmanager.TipManager { ...@@ -217,7 +223,35 @@ func TipManager() *tipmanager.TipManager {
// ValueObjectFactory returns the ValueObjectFactory singleton. // ValueObjectFactory returns the ValueObjectFactory singleton.
func ValueObjectFactory() *tangle.ValueObjectFactory { func ValueObjectFactory() *tangle.ValueObjectFactory {
valueObjectFactoryOnce.Do(func() { valueObjectFactoryOnce.Do(func() {
valueObjectFactory = tangle.NewValueObjectFactory(TipManager()) valueObjectFactory = tangle.NewValueObjectFactory(Tangle(), TipManager())
}) })
return valueObjectFactory return valueObjectFactory
} }
// AwaitTransactionToBeBooked awaits maxAwait for the given transaction to get booked.
func AwaitTransactionToBeBooked(txID transaction.ID, maxAwait time.Duration) error {
booked := make(chan struct{}, 1)
// exit is used to let the caller exit if for whatever
// reason the same transaction gets booked multiple times
exit := make(chan struct{})
defer close(exit)
closure := events.NewClosure(func(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *tangle.CachedTransactionMetadata, decisionPending bool) {
defer cachedTransaction.Release()
defer cachedTransactionMetadata.Release()
if cachedTransaction.Unwrap().ID() != txID {
return
}
select {
case booked <- struct{}{}:
case <-exit:
}
})
Tangle().Events.TransactionBooked.Attach(closure)
defer Tangle().Events.TransactionBooked.Detach(closure)
select {
case <-time.After(maxAwait):
return ErrTransactionWasNotBookedInTime
case <-booked:
return nil
}
}
...@@ -11,4 +11,7 @@ var ( ...@@ -11,4 +11,7 @@ var (
// ErrPayloadInvalid represents an error type that is triggered when an invalid payload is detected. // ErrPayloadInvalid represents an error type that is triggered when an invalid payload is detected.
ErrPayloadInvalid = errors.New("payload invalid") ErrPayloadInvalid = errors.New("payload invalid")
// ErrDoubleSpendForbidden represents an error that is triggered when a user tries to issue a double spend.
ErrDoubleSpendForbidden = errors.New("it is not allowed to issue a double spend")
) )
...@@ -9,13 +9,15 @@ import ( ...@@ -9,13 +9,15 @@ import (
// ValueObjectFactory acts as a factory to create new value objects. // ValueObjectFactory acts as a factory to create new value objects.
type ValueObjectFactory struct { type ValueObjectFactory struct {
tangle *Tangle
tipManager *tipmanager.TipManager tipManager *tipmanager.TipManager
Events *ValueObjectFactoryEvents Events *ValueObjectFactoryEvents
} }
// NewValueObjectFactory creates a new ValueObjectFactory. // NewValueObjectFactory creates a new ValueObjectFactory.
func NewValueObjectFactory(tipManager *tipmanager.TipManager) *ValueObjectFactory { func NewValueObjectFactory(tangle *Tangle, tipManager *tipmanager.TipManager) *ValueObjectFactory {
return &ValueObjectFactory{ return &ValueObjectFactory{
tangle: tangle,
tipManager: tipManager, tipManager: tipManager,
Events: &ValueObjectFactoryEvents{ Events: &ValueObjectFactoryEvents{
ValueObjectConstructed: events.NewEvent(valueObjectConstructedEvent), ValueObjectConstructed: events.NewEvent(valueObjectConstructedEvent),
...@@ -25,13 +27,27 @@ func NewValueObjectFactory(tipManager *tipmanager.TipManager) *ValueObjectFactor ...@@ -25,13 +27,27 @@ func NewValueObjectFactory(tipManager *tipmanager.TipManager) *ValueObjectFactor
// IssueTransaction creates a new value object including tip selection and returns it. // IssueTransaction creates a new value object including tip selection and returns it.
// It also triggers the ValueObjectConstructed event once it's done. // It also triggers the ValueObjectConstructed event once it's done.
func (v *ValueObjectFactory) IssueTransaction(tx *transaction.Transaction) *payload.Payload { func (v *ValueObjectFactory) IssueTransaction(tx *transaction.Transaction) (valueObject *payload.Payload, err error) {
parent1, parent2 := v.tipManager.Tips() parent1, parent2 := v.tipManager.Tips()
valueObject := payload.New(parent1, parent2, tx) // check if the tx that is supposed to be issued is a double spend
tx.Inputs().ForEach(func(outputId transaction.OutputID) bool {
v.tangle.TransactionOutput(outputId).Consume(func(output *Output) {
if output.ConsumerCount() >= 1 {
err = ErrDoubleSpendForbidden
}
})
return err == nil
})
if err != nil {
return
}
valueObject = payload.New(parent1, parent2, tx)
v.Events.ValueObjectConstructed.Trigger(valueObject) v.Events.ValueObjectConstructed.Trigger(valueObject)
return valueObject return
} }
// ValueObjectFactoryEvents represent events happening on a ValueObjectFactory. // ValueObjectFactoryEvents represent events happening on a ValueObjectFactory.
......
...@@ -104,7 +104,7 @@ func (inputs *Inputs) Bytes() (bytes []byte) { ...@@ -104,7 +104,7 @@ func (inputs *Inputs) Bytes() (bytes []byte) {
// ForEach iterates through the referenced Outputs and calls the consumer function for every Output. The iteration can // ForEach iterates through the referenced Outputs and calls the consumer function for every Output. The iteration can
// be aborted by returning false in the consumer. // be aborted by returning false in the consumer.
func (inputs *Inputs) ForEach(consumer func(outputId OutputID) bool) bool { func (inputs *Inputs) ForEach(consumer func(outputID OutputID) bool) bool {
return inputs.OrderedMap.ForEach(func(key, value interface{}) bool { return inputs.OrderedMap.ForEach(func(key, value interface{}) bool {
return value.(*orderedmap.OrderedMap).ForEach(func(key, value interface{}) bool { return value.(*orderedmap.OrderedMap).ForEach(func(key, value interface{}) bool {
return consumer(value.(OutputID)) return consumer(value.(OutputID))
......
...@@ -2,6 +2,8 @@ package sendtransaction ...@@ -2,6 +2,8 @@ package sendtransaction
import ( import (
"net/http" "net/http"
"sync"
"time"
"github.com/iotaledger/goshimmer/dapps/valuetransfers" "github.com/iotaledger/goshimmer/dapps/valuetransfers"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction" "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
...@@ -9,8 +11,16 @@ import ( ...@@ -9,8 +11,16 @@ import (
"github.com/labstack/echo" "github.com/labstack/echo"
) )
var (
sendTxMu sync.Mutex
maxBookedAwaitTime = 5 * time.Second
)
// Handler sends a transaction. // Handler sends a transaction.
func Handler(c echo.Context) error { func Handler(c echo.Context) error {
sendTxMu.Lock()
defer sendTxMu.Unlock()
var request Request var request Request
if err := c.Bind(&request); err != nil { if err := c.Bind(&request); err != nil {
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()}) return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
...@@ -28,12 +38,18 @@ func Handler(c echo.Context) error { ...@@ -28,12 +38,18 @@ func Handler(c echo.Context) error {
} }
// Prepare value payload and send the message to tangle // Prepare value payload and send the message to tangle
payload := valuetransfers.ValueObjectFactory().IssueTransaction(tx) payload, err := valuetransfers.ValueObjectFactory().IssueTransaction(tx)
if err != nil {
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
_, err = issuer.IssuePayload(payload) _, err = issuer.IssuePayload(payload)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()}) return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
} }
if err := valuetransfers.AwaitTransactionToBeBooked(tx.ID(), maxBookedAwaitTime); err != nil {
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
return c.JSON(http.StatusOK, Response{TransactionID: tx.ID().String()}) return c.JSON(http.StatusOK, Response{TransactionID: tx.ID().String()})
} }
......
...@@ -66,8 +66,11 @@ func Handler(c echo.Context) error { ...@@ -66,8 +66,11 @@ func Handler(c echo.Context) error {
tx := transaction.New(inputs, outputs) tx := transaction.New(inputs, outputs)
// Prepare value payload and send the message to tangle // Prepare value payload and send the message to tangle
payload := valuetransfers.ValueObjectFactory().IssueTransaction(tx) payload, err := valuetransfers.ValueObjectFactory().IssueTransaction(tx)
_, err := issuer.IssuePayload(payload) if err != nil {
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
_, err = issuer.IssuePayload(payload)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()}) return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment