Skip to content

Commit

Permalink
Merge pull request #1476 from iotaledger/develop
Browse files Browse the repository at this point in the history
Merge v0.7.2 changes to master
  • Loading branch information
capossele authored Jun 17, 2021
2 parents 8fca0bb + a6c40c4 commit 2db0ccd
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 25 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# v0.7.2 - 2021-06-17
* Add local double spend filter in webAPI
* Add SetBranchFinalized to FCoB
* Set message of the weak parents finalized rather than only their payload
* Update snapshot file with DevNet UTXO at 2021-06-17 08:52 UTC
* Update JS dependencies
* **Breaking**: bumps network and database versions

# v0.7.1 - 2021-06-15
* Improve Faucet
* Improve docs
Expand Down
6 changes: 6 additions & 0 deletions packages/consensus/fcob/consensusmechanism.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,12 @@ func (f *ConsensusMechanism) onPayloadOpinionFormed(messageID tangle.MessageID,
if err != nil {
panic(err)
}
if !liked {
_, err := f.tangle.LedgerState.BranchDAG.SetBranchFinalized(f.tangle.LedgerState.BranchID(transactionID), true)
if err != nil {
panic(err)
}
}
}
})
})
Expand Down
6 changes: 3 additions & 3 deletions plugins/analysis/dashboard/frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5125,9 +5125,9 @@ postcss-value-parser@^4.1.0:
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==

postcss@^7.0.14, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6:
version "7.0.35"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24"
integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==
version "7.0.36"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb"
integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"
Expand Down
2 changes: 1 addition & 1 deletion plugins/autopeering/discovery/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "github.com/iotaledger/hive.go/configuration"
// Parameters contains the configuration parameters used by the message layer.
var Parameters = struct {
// NetworkVersion defines the config flag of the network version.
NetworkVersion int `default:"34" usage:"autopeering network version"`
NetworkVersion int `default:"35" usage:"autopeering network version"`

// EntryNodes defines the config flag of the entry nodes.
EntryNodes []string `default:"[email protected]:15626,5EDH4uY78EA6wrBkHHAVBWBMDt7EcksRq6pjzipoW15B@entryshimmer.tanglebay.com:14646" usage:"list of trusted entry nodes for auto peering"`
Expand Down
2 changes: 1 addition & 1 deletion plugins/banner/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var (
once sync.Once

// AppVersion version number
AppVersion = "v0.7.1"
AppVersion = "v0.7.2"
// SimplifiedAppVersion is the version number without commit hash
SimplifiedAppVersion = simplifiedVersion(AppVersion)
)
Expand Down
2 changes: 1 addition & 1 deletion plugins/database/versioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
const (
// DBVersion defines the version of the database schema this version of GoShimmer supports.
// Every time there's a breaking change regarding the stored data, this version flag should be adjusted.
DBVersion = 36
DBVersion = 37
)

var (
Expand Down
4 changes: 3 additions & 1 deletion plugins/messagelayer/approvalweight.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ func propagateFinalizedApprovalWeight(message *tangle.Message, messageMetadata *

// mark weak parents as finalized but not propagate finalized flag to its past cone
message.ForEachWeakParent(func(parentID tangle.MessageID) {
setPayloadFinalized(parentID)
Tangle().Storage.MessageMetadata(parentID).Consume(func(messageMetadata *tangle.MessageMetadata) {
setMessageFinalized(messageMetadata)
})
})

// propagate finalized to strong parents
Expand Down
121 changes: 121 additions & 0 deletions plugins/webapi/ledgerstate/doublespend_filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package ledgerstate

import (
"bytes"
"sync"
"time"

"github.com/iotaledger/goshimmer/packages/clock"
"github.com/iotaledger/goshimmer/packages/ledgerstate"
)

// DoubleSpendFilter keeps a log of recently submitted transactions and their consumed outputs.
type DoubleSpendFilter struct {
recentMap map[ledgerstate.OutputID]ledgerstate.TransactionID
addedAt map[ledgerstate.TransactionID]time.Time
mutex sync.RWMutex
}

// NewDoubleSpendFilter creates a new doubleSpendFilter worker.
func NewDoubleSpendFilter() *DoubleSpendFilter {
return &DoubleSpendFilter{
recentMap: map[ledgerstate.OutputID]ledgerstate.TransactionID{},
addedAt: map[ledgerstate.TransactionID]time.Time{},
}
}

// Add adds a transaction and it's consumed inputs to the doubleSpendFilter.
func (d *DoubleSpendFilter) Add(tx *ledgerstate.Transaction) {
d.mutex.Lock()
defer d.mutex.Unlock()
now := clock.SyncedTime()
for _, input := range tx.Essence().Inputs() {
if input.Type() != ledgerstate.UTXOInputType {
continue
}
casted := input.(*ledgerstate.UTXOInput)
if casted == nil {
continue
}
d.recentMap[casted.ReferencedOutputID()] = tx.ID()
d.addedAt[tx.ID()] = now
}
}

// Remove removes all outputs associated to the given transaction ID.
func (d *DoubleSpendFilter) Remove(txID ledgerstate.TransactionID) {
d.mutex.Lock()
defer d.mutex.Unlock()
if len(d.recentMap) == 0 {
return
}
if _, has := d.addedAt[txID]; !has {
return
}
d.remove(txID)
d.shrinkMaps()
}

// HasConflict returns if there is a conflicting output in the internal map wrt to the provided inputs (outputIDs).
func (d *DoubleSpendFilter) HasConflict(outputs ledgerstate.Inputs) (bool, ledgerstate.TransactionID) {
d.mutex.RLock()
defer d.mutex.RUnlock()

for _, input := range outputs {
if input.Type() != ledgerstate.UTXOInputType {
continue
}
casted := input.(*ledgerstate.UTXOInput)
if casted == nil {
continue
}
if txID, has := d.recentMap[casted.ReferencedOutputID()]; has {
return true, txID
}
}
return false, ledgerstate.TransactionID{}
}

// CleanUp removes transactions from the DoubleSpendFilter if they were added more, than 30s ago.
func (d *DoubleSpendFilter) CleanUp() {
d.mutex.Lock()
defer d.mutex.Unlock()
// early return if there is nothing to be cleaned up
if len(d.addedAt) == 0 {
return
}
now := clock.SyncedTime()
for txID, addedTime := range d.addedAt {
if now.Sub(addedTime) > DoubleSpendFilterCleanupInterval {
d.remove(txID)
}
}
d.shrinkMaps()
}

// remove is a non-concurrency safe internal method.
func (d *DoubleSpendFilter) remove(txID ledgerstate.TransactionID) {
// remove all outputs
for outputID, storedTxID := range d.recentMap {
if bytes.Equal(txID.Bytes(), storedTxID.Bytes()) {
delete(d.recentMap, outputID)
}
}
delete(d.addedAt, txID)
}

// shrinkMaps is a non-concurrency safe internal method.
func (d *DoubleSpendFilter) shrinkMaps() {
shrunkRecent := map[ledgerstate.OutputID]ledgerstate.TransactionID{}
shrunkAddedAt := map[ledgerstate.TransactionID]time.Time{}

for key, value := range d.recentMap {
shrunkRecent[key] = value
}
for key, value := range d.addedAt {
shrunkAddedAt[key] = value
}

d.recentMap = shrunkRecent
d.addedAt = shrunkAddedAt
}
111 changes: 93 additions & 18 deletions plugins/webapi/ledgerstate/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"time"

"github.com/cockroachdb/errors"
"github.com/iotaledger/hive.go/daemon"
"github.com/iotaledger/hive.go/events"
"github.com/iotaledger/hive.go/logger"
"github.com/iotaledger/hive.go/node"
"github.com/labstack/echo"

Expand All @@ -15,46 +18,107 @@ import (
"github.com/iotaledger/goshimmer/packages/jsonmodels"
"github.com/iotaledger/goshimmer/packages/ledgerstate"
"github.com/iotaledger/goshimmer/packages/mana"
"github.com/iotaledger/goshimmer/packages/shutdown"
"github.com/iotaledger/goshimmer/packages/tangle"
"github.com/iotaledger/goshimmer/plugins/messagelayer"
"github.com/iotaledger/goshimmer/plugins/webapi"
)

// region Plugin ///////////////////////////////////////////////////////////////////////////////////////////////////////

// PluginName is the name of the web API plugin.
const (
PluginName = "WebAPI ledgerstate Endpoint"
DoubleSpendFilterCleanupInterval = 10 * time.Second
)

var (
// plugin holds the singleton instance of the plugin.
plugin *node.Plugin

// pluginOnce is used to ensure that the plugin is a singleton.
pluginOnce sync.Once

// doubleSpendFilter helps to filter out double spends locally.
doubleSpendFilter *DoubleSpendFilter

// doubleSpendFilterOnce ensures that doubleSpendFilter is a singleton.
doubleSpendFilterOnce sync.Once

// closure to be executed on transaction confirmation.
onTransactionConfirmedClosure *events.Closure

// logger
log *logger.Logger
)

// Plugin returns the plugin as a singleton.
func Plugin() *node.Plugin {
pluginOnce.Do(func() {
plugin = node.NewPlugin("WebAPI ledgerstate Endpoint", node.Enabled, func(*node.Plugin) {
webapi.Server().GET("ledgerstate/addresses/:address", GetAddress)
webapi.Server().GET("ledgerstate/addresses/:address/unspentOutputs", GetAddressUnspentOutputs)
webapi.Server().POST("ledgerstate/addresses/unspentOutputs", PostAddressUnspentOutputs)
webapi.Server().GET("ledgerstate/branches/:branchID", GetBranch)
webapi.Server().GET("ledgerstate/branches/:branchID/children", GetBranchChildren)
webapi.Server().GET("ledgerstate/branches/:branchID/conflicts", GetBranchConflicts)
webapi.Server().GET("ledgerstate/outputs/:outputID", GetOutput)
webapi.Server().GET("ledgerstate/outputs/:outputID/consumers", GetOutputConsumers)
webapi.Server().GET("ledgerstate/outputs/:outputID/metadata", GetOutputMetadata)
webapi.Server().GET("ledgerstate/transactions/:transactionID", GetTransaction)
webapi.Server().GET("ledgerstate/transactions/:transactionID/metadata", GetTransactionMetadata)
webapi.Server().GET("ledgerstate/transactions/:transactionID/inclusionState", GetTransactionInclusionState)
webapi.Server().GET("ledgerstate/transactions/:transactionID/consensus", GetTransactionConsensusMetadata)
webapi.Server().GET("ledgerstate/transactions/:transactionID/attachments", GetTransactionAttachments)
webapi.Server().POST("ledgerstate/transactions", PostTransaction)
})
plugin = node.NewPlugin(PluginName, node.Enabled, configure, run)
})

return plugin
}

// Filter returns the double spend filter singleton.
func Filter() *DoubleSpendFilter {
doubleSpendFilterOnce.Do(func() {
doubleSpendFilter = NewDoubleSpendFilter()
})
return doubleSpendFilter
}

func configure(*node.Plugin) {
doubleSpendFilter = Filter()
onTransactionConfirmedClosure = events.NewClosure(func(transactionID ledgerstate.TransactionID) {
doubleSpendFilter.Remove(transactionID)
})
messagelayer.Tangle().LedgerState.UTXODAG.Events.TransactionConfirmed.Attach(onTransactionConfirmedClosure)
log = logger.NewLogger(PluginName)
}

func run(*node.Plugin) {
if err := daemon.BackgroundWorker("WebAPI Double Spend Filter", worker, shutdown.PriorityWebAPI); err != nil {
log.Panicf("Failed to start as daemon: %s", err)
}

// register endpoints
webapi.Server().GET("ledgerstate/addresses/:address", GetAddress)
webapi.Server().GET("ledgerstate/addresses/:address/unspentOutputs", GetAddressUnspentOutputs)
webapi.Server().POST("ledgerstate/addresses/unspentOutputs", PostAddressUnspentOutputs)
webapi.Server().GET("ledgerstate/branches/:branchID", GetBranch)
webapi.Server().GET("ledgerstate/branches/:branchID/children", GetBranchChildren)
webapi.Server().GET("ledgerstate/branches/:branchID/conflicts", GetBranchConflicts)
webapi.Server().GET("ledgerstate/outputs/:outputID", GetOutput)
webapi.Server().GET("ledgerstate/outputs/:outputID/consumers", GetOutputConsumers)
webapi.Server().GET("ledgerstate/outputs/:outputID/metadata", GetOutputMetadata)
webapi.Server().GET("ledgerstate/transactions/:transactionID", GetTransaction)
webapi.Server().GET("ledgerstate/transactions/:transactionID/metadata", GetTransactionMetadata)
webapi.Server().GET("ledgerstate/transactions/:transactionID/inclusionState", GetTransactionInclusionState)
webapi.Server().GET("ledgerstate/transactions/:transactionID/consensus", GetTransactionConsensusMetadata)
webapi.Server().GET("ledgerstate/transactions/:transactionID/attachments", GetTransactionAttachments)
webapi.Server().POST("ledgerstate/transactions", PostTransaction)
}

func worker(shutdownSignal <-chan struct{}) {
defer log.Infof("Stopping %s ... done", PluginName)
func() {
ticker := time.NewTicker(DoubleSpendFilterCleanupInterval)
defer ticker.Stop()
for {
select {
case <-shutdownSignal:
return
case <-ticker.C:
doubleSpendFilter.CleanUp()
}
}
}()
log.Infof("Stopping %s ...", PluginName)
messagelayer.Tangle().LedgerState.UTXODAG.Events.TransactionConfirmed.Detach(onTransactionConfirmedClosure)
}

// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////

// region GetAddress ///////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -421,7 +485,7 @@ func branchIDFromContext(c echo.Context) (branchID ledgerstate.BranchID, err err

// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////

// region postTransaction //////////////////////////////////////////////////////////////////////////////////////////////
// region PostTransaction //////////////////////////////////////////////////////////////////////////////////////////////

const maxBookedAwaitTime = 5 * time.Second

Expand All @@ -441,6 +505,13 @@ func PostTransaction(c echo.Context) error {
return c.JSON(http.StatusBadRequest, &jsonmodels.PostTransactionResponse{Error: err.Error()})
}

// check if it would introduce a double spend known to the node locally
has, conflictingID := doubleSpendFilter.HasConflict(tx.Essence().Inputs())
if has {
err = errors.Errorf("transaction is conflicting with previously submitted transaction %s", conflictingID.Base58())
return c.JSON(http.StatusBadRequest, &jsonmodels.PostTransactionResponse{Error: err.Error()})
}

// validate allowed mana pledge nodes.
allowedAccessMana := messagelayer.GetAllowedPledgeNodes(mana.AccessMana)
if allowedAccessMana.IsFilterEnabled {
Expand Down Expand Up @@ -481,7 +552,11 @@ func PostTransaction(c echo.Context) error {
return messagelayer.Tangle().IssuePayload(tx)
}

// add tx to double spend doubleSpendFilter
doubleSpendFilter.Add(tx)
if _, err := messagelayer.AwaitMessageToBeBooked(issueTransaction, tx.ID(), maxBookedAwaitTime); err != nil {
// if we failed to issue the transaction, we remove it
doubleSpendFilter.Remove(tx.ID())
return c.JSON(http.StatusBadRequest, jsonmodels.PostTransactionResponse{Error: err.Error()})
}
return c.JSON(http.StatusOK, &jsonmodels.PostTransactionResponse{TransactionID: tx.ID().Base58()})
Expand Down
Binary file modified snapshot.bin
Binary file not shown.

0 comments on commit 2db0ccd

Please sign in to comment.