solidifier.go 7.66 KiB
package tangle
import (
"runtime"
"time"
"github.com/iotaledger/goshimmer/packages/errors"
"github.com/iotaledger/goshimmer/packages/gossip"
"github.com/iotaledger/goshimmer/packages/model/approvers"
"github.com/iotaledger/goshimmer/packages/model/meta_transaction"
"github.com/iotaledger/goshimmer/packages/model/transactionmetadata"
"github.com/iotaledger/goshimmer/packages/model/value_transaction"
"github.com/iotaledger/goshimmer/packages/workerpool"
"github.com/iotaledger/hive.go/daemon"
"github.com/iotaledger/hive.go/events"
"github.com/iotaledger/iota.go/trinary"
)
// region plugin module setup //////////////////////////////////////////////////////////////////////////////////////////
const UnsolidInterval = 30
var (
workerCount = runtime.NumCPU()
workerPool *workerpool.WorkerPool
unsolidTxs *UnsolidTxs
requester Requester
)
func SetRequester(req Requester) {
requester = req
}
func configureSolidifier() {
workerPool = workerpool.New(func(task workerpool.Task) {
processMetaTransaction(task.Param(0).(*meta_transaction.MetaTransaction))
task.Return(nil)
}, workerpool.WorkerCount(workerCount), workerpool.QueueSize(10000))
unsolidTxs = NewUnsolidTxs()
gossip.Events.TransactionReceived.Attach(events.NewClosure(func(ev *gossip.TransactionReceivedEvent) {
metaTx := meta_transaction.FromBytes(ev.Data)
if err := metaTx.Validate(); err != nil {
log.Warnf("invalid transaction: %s", err)
return
}
workerPool.Submit(metaTx)
}))
}
func runSolidifier() {
log.Info("Starting Solidifier ...")
daemon.BackgroundWorker("Tangle Solidifier", func(shutdownSignal <-chan struct{}) {
log.Info("Starting Solidifier ... done")
workerPool.Start()
<-shutdownSignal
log.Info("Stopping Solidifier ...")
workerPool.StopAndWait()
log.Info("Stopping Solidifier ... done")
})
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Checks and updates the solid flag of a single transaction.
func checkSolidity(transaction *value_transaction.ValueTransaction) (result bool, err errors.IdentifiableError) {
// abort if transaction is solid already
txMetadata, metaDataErr := GetTransactionMetadata(transaction.GetHash(), transactionmetadata.New)
if metaDataErr != nil {
err = metaDataErr
return
} else if txMetadata.GetSolid() {
result = true
return
}
// check solidity of branch transaction if it is not genesis
if branchTransactionHash := transaction.GetBranchTransactionHash(); branchTransactionHash != meta_transaction.BRANCH_NULL_HASH {
// abort if branch transaction is missing
if branchTransaction, branchErr := GetTransaction(branchTransactionHash); branchErr != nil {
err = branchErr
return
} else if branchTransaction == nil {
//log.Info("Missing Branch (nil)", transaction.GetValue(), "Missing ", transaction.GetBranchTransactionHash())
// add BranchTransactionHash to the unsolid txs and send a transaction request
unsolidTxs.Add(transaction.GetBranchTransactionHash())
requestTransaction(transaction.GetBranchTransactionHash())
return
} else if branchTransactionMetadata, branchErr := GetTransactionMetadata(branchTransaction.GetHash(), transactionmetadata.New); branchErr != nil {
err = branchErr
//log.Info("Missing Branch", transaction.GetValue())
return
} else if !branchTransactionMetadata.GetSolid() {
return
}
}
// check solidity of trunk transaction if it is not genesis
if trunkTransactionHash := transaction.GetTrunkTransactionHash(); trunkTransactionHash != meta_transaction.BRANCH_NULL_HASH {
if trunkTransaction, trunkErr := GetTransaction(trunkTransactionHash); trunkErr != nil {
err = trunkErr
return
} else if trunkTransaction == nil {
//log.Info("Missing Trunk (nil)", transaction.GetValue())
// add TrunkTransactionHash to the unsolid txs and send a transaction request
unsolidTxs.Add(transaction.GetTrunkTransactionHash())
requestTransaction(transaction.GetTrunkTransactionHash())
return
} else if trunkTransactionMetadata, trunkErr := GetTransactionMetadata(trunkTransaction.GetHash(), transactionmetadata.New); trunkErr != nil {
err = trunkErr
//log.Info("Missing Trunk", transaction.GetValue())
return
} else if !trunkTransactionMetadata.GetSolid() {
return
}
}
// mark transaction as solid and trigger event
if txMetadata.SetSolid(true) {
Events.TransactionSolid.Trigger(transaction)
}
result = true
return
}
// Checks and updates the solid flag of a transaction and its approvers (future cone).
func IsSolid(transaction *value_transaction.ValueTransaction) (bool, errors.IdentifiableError) {
if isSolid, err := checkSolidity(transaction); err != nil {
return false, err
} else if isSolid {
//log.Info("Solid ", transaction.GetValue())
if err := propagateSolidity(transaction.GetHash()); err != nil {
return false, err //should we return true?
}
return true, nil
}
//log.Info("Not solid ", transaction.GetValue())
return false, nil
}
func propagateSolidity(transactionHash trinary.Trytes) errors.IdentifiableError {
if transactionApprovers, err := GetApprovers(transactionHash, approvers.New); err != nil {
return err
} else {
for _, approverHash := range transactionApprovers.GetHashes() {
if approver, err := GetTransaction(approverHash); err != nil {
return err
} else if approver != nil {
if isSolid, err := checkSolidity(approver); err != nil {
return err
} else if isSolid {
if err := propagateSolidity(approver.GetHash()); err != nil {
return err
}
}
}
}
}
return nil
}
func processMetaTransaction(metaTransaction *meta_transaction.MetaTransaction) {
var newTransaction bool
if tx, err := GetTransaction(metaTransaction.GetHash(), func(transactionHash trinary.Trytes) *value_transaction.ValueTransaction {
newTransaction = true
return value_transaction.FromMetaTransaction(metaTransaction)
}); err != nil {
log.Errorf("Unable to load transaction %s: %s", metaTransaction.GetHash(), err.Error())
} else if newTransaction {
updateUnsolidTxs(tx)
processTransaction(tx)
}
}
func processTransaction(transaction *value_transaction.ValueTransaction) {
Events.TransactionStored.Trigger(transaction)
// store transaction hash for address in DB
err := StoreTransactionHashForAddressInDatabase(
&TxHashForAddress{
Address: transaction.GetAddress(),
TxHash: transaction.GetHash(),
},
)
if err != nil {
log.Errorw(err.Error())
}
transactionHash := transaction.GetHash()
// register tx as approver for trunk
if trunkApprovers, err := GetApprovers(transaction.GetTrunkTransactionHash(), approvers.New); err != nil {
log.Errorf("Unable to get approvers of transaction %s: %s", transaction.GetTrunkTransactionHash(), err.Error())
return
} else {
trunkApprovers.Add(transactionHash)
}
// register tx as approver for branch
if branchApprovers, err := GetApprovers(transaction.GetBranchTransactionHash(), approvers.New); err != nil {
log.Errorf("Unable to get approvers of transaction %s: %s", transaction.GetBranchTransactionHash(), err.Error())
return
} else {
branchApprovers.Add(transactionHash)
}
// update the solidity flags of this transaction and its approvers
_, err = IsSolid(transaction)
if err != nil {
log.Errorf("Unable to check solidity: %s", err.Error())
return
}
}
func updateUnsolidTxs(tx *value_transaction.ValueTransaction) {
unsolidTxs.Remove(tx.GetHash())
targetTime := time.Now().Add(time.Duration(-UnsolidInterval) * time.Second)
txs := unsolidTxs.Update(targetTime)
for _, tx := range txs {
requestTransaction(tx)
}
}
func requestTransaction(hash trinary.Trytes) {
if requester == nil {
return
}
log.Debugw("Requesting tx", "hash", hash)
requester.RequestTransaction(hash)
}