Skip to content
Snippets Groups Projects
Commit bc08c0b6 authored by Hans Moog's avatar Hans Moog
Browse files

Feat: added proof-of-concept of otv consensus

parent 437bf6b9
No related branches found
No related tags found
No related merge requests found
Showing
with 925 additions and 0 deletions
package social_consensus
import (
"sync"
)
type Approvers struct {
transactionID int
transactionLikers map[int]*Transaction
transactionDislikers map[int]*Transaction
realityLikers map[int]*Transaction
realityDislikers map[int]*Transaction
mutex sync.RWMutex
}
func NewApprovers(transactionID int) *Approvers {
return &Approvers{
transactionID: transactionID,
transactionLikers: make(map[int]*Transaction),
transactionDislikers: make(map[int]*Transaction),
realityLikers: make(map[int]*Transaction),
realityDislikers: make(map[int]*Transaction),
}
}
func (approvers *Approvers) GetTransactionID() int {
return approvers.transactionID
}
func (approvers *Approvers) AddTransactionLiker(transaction *Transaction) {
approvers.mutex.Lock()
defer approvers.mutex.Unlock()
approvers.transactionLikers[transaction.GetID()] = transaction
}
func (approvers *Approvers) AddTransactionDisliker(transaction *Transaction) {
approvers.mutex.Lock()
defer approvers.mutex.Unlock()
approvers.transactionDislikers[transaction.GetID()] = transaction
}
func (approvers *Approvers) AddRealityLiker(transaction *Transaction) {
approvers.mutex.Lock()
defer approvers.mutex.Unlock()
approvers.realityLikers[transaction.GetID()] = transaction
}
func (approvers *Approvers) AddRealityDisliker(transaction *Transaction) {
approvers.mutex.Lock()
defer approvers.mutex.Unlock()
approvers.realityDislikers[transaction.GetID()] = transaction
}
package social_consensus
import (
"sync"
)
type ApproversDatabase struct {
approvers map[int]*Approvers
mutex sync.RWMutex
}
func NewApproversDatabase() *ApproversDatabase {
return &ApproversDatabase{
approvers: make(map[int]*Approvers),
}
}
func (approversDatabase *ApproversDatabase) StoreApprovers(approvers *Approvers) {
approversDatabase.mutex.Lock()
defer approversDatabase.mutex.Unlock()
approversDatabase.approvers[approvers.GetTransactionID()] = approvers
}
func (approversDatabase *ApproversDatabase) LoadApprovers(transactionID int) *Approvers {
approversDatabase.mutex.RLock()
defer approversDatabase.mutex.RUnlock()
return approversDatabase.approvers[transactionID]
}
package social_consensus
type ConflictSet struct {
realities map[int]*Reality
}
func NewConflictSet() *ConflictSet {
return &ConflictSet{
realities: make(map[int]*Reality),
}
}
func (conflictSet *ConflictSet) GetRealities() map[int]*Reality {
return conflictSet.realities
}
func (conflictSet *ConflictSet) GetReality(id int) *Reality {
return conflictSet.realities[id]
}
func (conflictSet *ConflictSet) AddReality(id int) (result *Reality) {
if _, exists := conflictSet.realities[id]; !exists {
result = NewReality(conflictSet, id)
conflictSet.realities[id] = result
}
return
}
package social_consensus
import (
"fmt"
)
type ElderMask uint64
func (elderMask ElderMask) Union(otherElderMask ElderMask) ElderMask {
return elderMask | otherElderMask
}
func (elderMask ElderMask) Contains(otherMask ElderMask) bool {
return elderMask&otherMask != 0
}
func (elderMask ElderMask) String() string {
return "ElderMask(" + fmt.Sprintf("%064b", elderMask) + ")"
}
package social_consensus
import (
"math/rand"
"time"
"github.com/iotaledger/goshimmer/packages/events"
)
type Node struct {
id int
weight int
transactionCounter int
tipSelector *TipSelector
solidifier *Solidifier
approversDatabase *ApproversDatabase
transactionDatabase *TransactionDatabase
gossipNeighbors []*Node
conflictSet *ConflictSet
Events NodeEvents
favoredReality int
}
var nodeIdCounter = 0
func NewNode(weight int) *Node {
node := &Node{
id: nodeIdCounter,
weight: weight,
approversDatabase: NewApproversDatabase(),
transactionDatabase: NewTransactionDatabase(),
gossipNeighbors: make([]*Node, 0),
conflictSet: NewConflictSet(),
Events: NodeEvents{
TransactionSolid: events.NewEvent(TransactionEventHandler),
},
}
node.Events.TransactionSolid.Attach(events.NewClosure(node.ClassifyTransaction))
node.tipSelector = NewTipSelector(node)
node.solidifier = NewSolidifier(node)
nodeIdCounter++
return node
}
func (node *Node) GetWeight() int {
return node.weight
}
func (node *Node) GetID() int {
return node.id
}
func (node *Node) Peer(nodes ...*Node) {
node.gossipNeighbors = nodes
}
func (node *Node) GossipTransaction(transaction *Transaction) {
node.ReceiveTransaction(transaction)
for _, neighbor := range node.gossipNeighbors {
go func(neighbor *Node, transaction *Transaction) {
time.Sleep(time.Duration(100+rand.Intn(200)) * time.Millisecond)
neighbor.ReceiveTransaction(transaction.Clone())
}(neighbor, transaction)
}
}
func (node *Node) StoreTransaction(transaction *Transaction) {
if transaction != nil {
node.transactionDatabase.StoreTransaction(transaction)
}
}
func (node *Node) GetTransaction(id int) *Transaction {
return node.transactionDatabase.LoadTransaction(id)
}
func (node *Node) CreateTransaction() *Transaction {
trunk, branch, transactionLiked, realityLiked := node.tipSelector.GetTipsToApprove()
newTransaction := NewTransaction()
newTransaction.SetCounter(node.transactionCounter)
newTransaction.SetNodeID(node.GetID())
newTransaction.SetTrunkID(trunk.GetID())
newTransaction.SetBranchID(branch.GetID())
newTransaction.SetBranchTransactionLiked(transactionLiked)
newTransaction.SetBranchRealityLiked(realityLiked)
node.transactionCounter++
return newTransaction
}
func (node *Node) IssueTransaction() {
node.GossipTransaction(node.CreateTransaction())
}
func (node *Node) updateApprovers(transaction *Transaction) {
if trunkID := transaction.GetTrunkID(); trunkID != -1 {
approvers := node.approversDatabase.LoadApprovers(trunkID)
if approvers == nil {
approvers = NewApprovers(trunkID)
node.approversDatabase.StoreApprovers(approvers)
}
approvers.AddTransactionLiker(transaction)
approvers.AddRealityLiker(transaction)
}
if branchID := transaction.GetBranchID(); branchID != -1 {
approvers := node.approversDatabase.LoadApprovers(branchID)
if approvers == nil {
approvers = NewApprovers(branchID)
node.approversDatabase.StoreApprovers(approvers)
}
if transaction.IsBranchTransactionLiked() {
approvers.AddTransactionLiker(transaction)
} else {
approvers.AddTransactionDisliker(transaction)
}
if transaction.IsBranchRealityLiked() {
approvers.AddRealityLiker(transaction)
} else {
approvers.AddRealityDisliker(transaction)
}
}
}
func (node *Node) ReceiveTransaction(transaction *Transaction) {
if node.transactionDatabase.LoadTransaction(transaction.GetID()) == nil {
if transaction.claimedReality != 0 {
node.conflictSet.AddReality(transaction.claimedReality)
if node.favoredReality == 0 {
node.favoredReality = transaction.claimedReality
}
}
node.StoreTransaction(transaction)
node.updateApprovers(transaction)
node.solidifier.ProcessTransaction(transaction)
}
}
func (node *Node) ClassifyTransaction(transaction *Transaction) {
transactionLocallyLiked := transaction.claimedReality == node.favoredReality || transaction.claimedReality == 0
realityLocallyLiked := transactionLocallyLiked && (transaction.GetMetadata().GetReality() == 0 || transaction.GetMetadata().GetReality() == node.favoredReality)
transaction.GetMetadata().SetTransactionLocallyLiked(transactionLocallyLiked)
transaction.GetMetadata().SetRealityLocallyLiked(realityLocallyLiked)
node.tipSelector.ProcessClassifiedTransaction(transaction)
}
package social_consensus
import (
"github.com/iotaledger/goshimmer/packages/events"
)
type NodeEvents struct {
TransactionSolid *events.Event
}
func TransactionEventHandler(handler interface{}, params ...interface{}) {
handler.(func(*Transaction))(params[0].(*Transaction))
}
package social_consensus
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/iotaledger/goshimmer/packages/events"
)
func GetRealityFLC(node *Node, transaction *Transaction) (result []*Transaction) {
result = make([]*Transaction, 1)
result[0] = transaction
traversalStack := make([]*Transaction, 0)
seenTransactions := make(map[int]bool)
if approvers := node.approversDatabase.LoadApprovers(transaction.GetID()); approvers != nil {
for _, approver := range approvers.transactionLikers {
if _, exists := seenTransactions[approver.GetID()]; !exists {
traversalStack = append(traversalStack, approver)
seenTransactions[approver.GetID()] = true
}
}
for _, approver := range approvers.realityLikers {
if _, exists := seenTransactions[approver.GetID()]; !exists {
traversalStack = append(traversalStack, approver)
seenTransactions[approver.GetID()] = true
}
}
}
for len(traversalStack) != 0 {
currentTransaction := traversalStack[0]
result = append(result, currentTransaction)
if approvers := node.approversDatabase.LoadApprovers(currentTransaction.GetID()); approvers != nil {
for _, approver := range approvers.realityLikers {
if _, seen := seenTransactions[approver.GetID()]; !seen {
traversalStack = append(traversalStack, approver)
seenTransactions[approver.GetID()] = true
}
}
}
traversalStack = traversalStack[1:]
}
return result
}
func BenchmarkTPS(b *testing.B) {
transactionIdCounter = 0
genesisTransaction := NewTransaction()
genesisTransaction.SetTrunkID(-1)
genesisTransaction.SetBranchID(-1)
genesisTransaction.GetMetadata().SetSolid(true)
node := NewNode(100)
node.StoreTransaction(genesisTransaction)
b.ResetTimer()
for i := 0; i < b.N; i++ {
node.IssueTransaction()
}
}
func TestTipSelector_GetTipsToApprove(t *testing.T) {
// region initialize nodes /////////////////////////////////////////////////////////////////////////////////////////
genesisTransaction := NewTransaction()
genesisTransaction.SetTrunkID(-1)
genesisTransaction.SetBranchID(-1)
genesisTransaction.GetMetadata().SetSolid(true)
node0 := NewNode(15)
node0.StoreTransaction(genesisTransaction)
node1 := NewNode(17)
node1.StoreTransaction(genesisTransaction)
node2 := NewNode(19)
node2.StoreTransaction(genesisTransaction)
node0.Peer(node1, node2)
node1.Peer(node0, node2)
node2.Peer(node0, node1)
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////
var wg sync.WaitGroup
node0.Events.TransactionSolid.Attach(events.NewClosure(func(transaction *Transaction) {
wg.Done()
}))
// region initialize consensus test conditions /////////////////////////////////////////////////////////////////////
// create first conflicting transaction
wg.Add(1)
conflictingTransaction0 := node0.CreateTransaction()
conflictingTransaction0.claimedReality = 1
// create second conflicting transaction
wg.Add(1)
conflictingTransaction1 := node1.CreateTransaction()
conflictingTransaction1.claimedReality = 2
// make nodes see the conflicts in different order
node0.GossipTransaction(conflictingTransaction0)
node1.GossipTransaction(conflictingTransaction1)
node2.GossipTransaction(conflictingTransaction0)
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////
// issue a few transactions
for i := 0; i < 30; i++ {
wg.Add(3)
node0.IssueTransaction()
node1.IssueTransaction()
node2.IssueTransaction()
time.Sleep(50 * time.Millisecond)
}
wg.Wait()
// check if consensus reached
assert.Equal(t, node0.favoredReality, node1.favoredReality)
assert.Equal(t, node1.favoredReality, node2.favoredReality)
}
package social_consensus
import (
"sync"
)
type Reality struct {
id int
conflictSet *ConflictSet
supporters map[int]int
weight int
mutex sync.RWMutex
}
func NewReality(conflictSet *ConflictSet, id int) *Reality {
return &Reality{
id: id,
conflictSet: conflictSet,
supporters: make(map[int]int),
}
}
func (reality *Reality) GetId() int {
return reality.id
}
func (reality *Reality) GetConflictSet() *ConflictSet {
return reality.conflictSet
}
func (reality *Reality) ContainsSupporter(nodeId int) (transactionCounter int) {
if currentTransactionCounter, supporterExists := reality.supporters[nodeId]; supporterExists {
transactionCounter = currentTransactionCounter
}
return
}
func (reality *Reality) AddSupporter(nodeId int, transactionCounter int) {
reality.mutex.Lock()
defer reality.mutex.Unlock()
if oldTransactionCounter, supporterExists := reality.supporters[nodeId]; !supporterExists {
// remove supporter from the alternative reality
for otherRealityId, otherReality := range reality.conflictSet.GetRealities() {
if otherTransactionCounter := otherReality.ContainsSupporter(nodeId); otherRealityId != reality.id && otherTransactionCounter != 0 {
otherReality.RemoveSupporter(nodeId, transactionCounter)
}
}
// add supporter to new reality
reality.supporters[nodeId] = transactionCounter
// update weight
reality.weight += 20
} else if oldTransactionCounter < transactionCounter {
// update transaction counter
reality.supporters[nodeId] = transactionCounter
}
}
func (reality *Reality) RemoveSupporter(nodeId int, transactionCounter int) {
reality.mutex.Lock()
defer reality.mutex.Unlock()
if oldTransactionCounter, supporterExists := reality.supporters[nodeId]; supporterExists && oldTransactionCounter < transactionCounter {
delete(reality.supporters, nodeId)
reality.weight -= 20
}
}
package social_consensus
type Solidifier struct {
node *Node
}
func NewSolidifier(node *Node) *Solidifier {
return &Solidifier{
node: node,
}
}
func (solidifier *Solidifier) IsTransactionSolid(transaction *Transaction) (isSolid bool) {
isSolid = true
if trunkID := transaction.GetTrunkID(); trunkID != -1 {
if trunk := solidifier.node.GetTransaction(trunkID); trunk == nil {
isSolid = false
} else if !trunk.GetMetadata().IsSolid() {
isSolid = false
}
}
if branchID := transaction.GetBranchID(); branchID != -1 {
if trunk := solidifier.node.GetTransaction(branchID); trunk == nil {
isSolid = false
} else if !trunk.GetMetadata().IsSolid() {
isSolid = false
}
}
return
}
func (solidifier *Solidifier) PropagateSolidity(transaction *Transaction) {
if !transaction.GetMetadata().IsSolid() {
transaction.GetMetadata().SetSolid(true)
if transaction.claimedReality != 0 {
transaction.GetMetadata().SetReality(transaction.claimedReality)
} else {
trunk := solidifier.node.GetTransaction(transaction.GetTrunkID())
trunkReality := trunk.GetMetadata().GetReality()
branch := solidifier.node.GetTransaction(transaction.GetBranchID())
branchReality := branch.GetMetadata().GetReality()
if transaction.IsBranchRealityLiked() && branchReality != 0 {
transaction.GetMetadata().SetReality(branchReality)
solidifier.node.conflictSet.GetReality(branchReality).AddSupporter(transaction.GetNodeID(), transaction.counter)
var heaviestReality *Reality
heaviestRealityWeight := 0
for _, reality := range solidifier.node.conflictSet.GetRealities() {
if reality.weight > heaviestRealityWeight {
heaviestReality = reality
heaviestRealityWeight = reality.weight
}
}
if solidifier.node.favoredReality != heaviestReality.id {
solidifier.node.favoredReality = heaviestReality.id
}
} else {
transaction.GetMetadata().SetReality(trunkReality)
}
}
solidifier.node.Events.TransactionSolid.Trigger(transaction)
if approvers := solidifier.node.approversDatabase.LoadApprovers(transaction.GetID()); approvers != nil {
for _, approver := range approvers.transactionLikers {
solidifier.PropagateSolidity(approver)
}
for _, approver := range approvers.transactionDislikers {
solidifier.PropagateSolidity(approver)
}
for _, approver := range approvers.realityLikers {
solidifier.PropagateSolidity(approver)
}
for _, approver := range approvers.realityDislikers {
solidifier.PropagateSolidity(approver)
}
}
}
}
func (solidifier *Solidifier) ProcessTransaction(transaction *Transaction) {
if solidifier.IsTransactionSolid(transaction) {
solidifier.PropagateSolidity(transaction)
}
}
package social_consensus
import (
"sync"
"github.com/iotaledger/goshimmer/packages/datastructure"
)
type TipSelector struct {
node *Node
likedTips *datastructure.RandomMap // like transaction / like reality
semiLikedTips *datastructure.RandomMap // like transaction / dislike reality
dislikedTips *datastructure.RandomMap // dislike transaction / dislike reality
// semiDislikedTips *datastructure.RandomMap // dislike transaction / like reality (doesn't occur: the only reason
// to dislike a transaction is if it is conflicting. This however means it spawns a reality we can not both like and
// dislike it at the same time.
mutex sync.RWMutex
}
func NewTipSelector(node *Node) *TipSelector {
return &TipSelector{
node: node,
likedTips: datastructure.NewRandomMap(),
semiLikedTips: datastructure.NewRandomMap(),
dislikedTips: datastructure.NewRandomMap(),
}
}
func (tipSelector *TipSelector) GetTipsToApprove() (trunkTransaction *Transaction, branchTransaction *Transaction, transactionLiked bool, realityLiked bool) {
tipSelector.mutex.RLock()
defer tipSelector.mutex.RUnlock()
trunkTransaction = tipSelector.getRandomLikedTip()
if branchTransaction = tipSelector.getRandomSemiLikedTip(); branchTransaction != nil {
transactionLiked = true
realityLiked = false
return
}
if branchTransaction = tipSelector.getRandomDislikedTip(); branchTransaction != nil {
transactionLiked = false
realityLiked = false
return
}
branchTransaction = tipSelector.getRandomLikedTip()
transactionLiked = true
realityLiked = true
return
}
func (tipSelector *TipSelector) ProcessClassifiedTransaction(transaction *Transaction) {
tipSelector.mutex.Lock()
defer tipSelector.mutex.Unlock()
transactionMetadata := transaction.GetMetadata()
if transactionLiked := transactionMetadata.IsTransactionLocallyLiked(); transactionLiked {
if realityLiked := transactionMetadata.IsRealityLocallyLiked(); realityLiked {
tipSelector.likedTips.Set(transaction.GetID(), transaction)
if trunk := tipSelector.node.GetTransaction(transaction.GetTrunkID()); trunk != nil {
trunkId := trunk.GetID()
tipSelector.likedTips.Delete(trunkId)
tipSelector.semiLikedTips.Delete(trunkId)
tipSelector.dislikedTips.Delete(trunkId)
}
if branch := tipSelector.node.GetTransaction(transaction.GetBranchID()); branch != nil {
branchId := branch.GetID()
tipSelector.likedTips.Delete(branchId)
tipSelector.semiLikedTips.Delete(branchId)
tipSelector.dislikedTips.Delete(branchId)
}
} else {
tipSelector.semiLikedTips.Set(transaction.GetID(), transaction)
}
} else {
if realityLiked := transactionMetadata.IsRealityLocallyLiked(); realityLiked {
panic("we should never dislike a transaction and at the same time like its reality")
} else {
tipSelector.dislikedTips.Set(transaction.GetID(), transaction)
}
}
//fmt.Println("TIP COUNT:", tipSelector.dislikedTips.Size()+tipSelector.semiLikedTips.Size()+tipSelector.likedTips.Size())
}
func (tipSelector *TipSelector) getRandomLikedTip() *Transaction {
if result := tipSelector.likedTips.RandomEntry(); result == nil {
return tipSelector.node.GetTransaction(0)
} else {
return result.(*Transaction)
}
}
func (tipSelector *TipSelector) getRandomSemiLikedTip() *Transaction {
if randomSemiLikedTip := tipSelector.semiLikedTips.RandomEntry(); randomSemiLikedTip != nil {
return randomSemiLikedTip.(*Transaction)
} else {
return nil
}
}
func (tipSelector *TipSelector) getRandomDislikedTip() *Transaction {
if randomDislikedTip := tipSelector.dislikedTips.RandomEntry(); randomDislikedTip != nil {
return randomDislikedTip.(*Transaction)
} else {
return nil
}
}
package social_consensus
import (
"github.com/iotaledger/goshimmer/packages/stringify"
)
type Transaction struct {
id int
nodeId int
trunkId int
branchId int
counter int
branchRealityLiked bool
branchTransactionLiked bool
metadata *TransactionMetadata
claimedReality int
}
var transactionIdCounter = 0
func NewTransaction() *Transaction {
transaction := &Transaction{
id: transactionIdCounter,
}
transactionIdCounter++
return transaction
}
func (transaction *Transaction) SetNodeID(nodeID int) {
transaction.nodeId = nodeID
}
func (transaction *Transaction) GetNodeID() int {
return transaction.nodeId
}
func (transaction *Transaction) GetID() int {
return transaction.id
}
func (transaction *Transaction) SetTrunkID(id int) {
transaction.trunkId = id
}
func (transaction *Transaction) GetTrunkID() int {
return transaction.trunkId
}
func (transaction *Transaction) SetBranchID(id int) {
transaction.branchId = id
}
func (transaction *Transaction) GetBranchID() int {
return transaction.branchId
}
func (transaction *Transaction) SetCounter(counter int) {
transaction.counter = counter
}
func (transaction *Transaction) GetCounter() int {
return transaction.counter
}
func (transaction *Transaction) SetBranchRealityLiked(liked bool) {
transaction.branchRealityLiked = liked
}
func (transaction *Transaction) IsBranchRealityLiked() bool {
return transaction.branchRealityLiked
}
func (transaction *Transaction) SetBranchTransactionLiked(liked bool) {
transaction.branchTransactionLiked = liked
}
func (transaction *Transaction) IsBranchTransactionLiked() bool {
return transaction.branchTransactionLiked
}
func (transaction *Transaction) GetMetadata() *TransactionMetadata {
if transaction.metadata == nil {
transaction.metadata = NewTransactionMetadata()
}
return transaction.metadata
}
func (transaction *Transaction) Clone() *Transaction {
return &Transaction{
id: transaction.id,
nodeId: transaction.nodeId,
trunkId: transaction.GetTrunkID(),
branchId: transaction.GetBranchID(),
counter: transaction.counter,
branchRealityLiked: transaction.branchRealityLiked,
branchTransactionLiked: transaction.branchTransactionLiked,
metadata: nil,
claimedReality: transaction.claimedReality,
}
}
func (transaction *Transaction) String() string {
return stringify.Struct("Transaction",
stringify.StructField("id", transaction.GetID()),
stringify.StructField("nodeID", transaction.GetNodeID()),
stringify.StructField("trunkID", transaction.GetTrunkID()),
stringify.StructField("branchID", transaction.GetBranchID()),
stringify.StructField("branchTransactionLiked", transaction.IsBranchTransactionLiked()),
stringify.StructField("branchRealityLiked", transaction.IsBranchRealityLiked()),
stringify.StructField("claimedReality", transaction.claimedReality),
stringify.StructField("reality", transaction.GetMetadata().GetReality()),
stringify.StructField("isTransactionLocallyLiked", transaction.GetMetadata().IsTransactionLocallyLiked()),
stringify.StructField("isRealityLocallyLiked", transaction.GetMetadata().IsRealityLocallyLiked()),
)
}
package social_consensus
import (
"sync"
)
type TransactionDatabase struct {
transactions map[int]*Transaction
mutex sync.RWMutex
}
func NewTransactionDatabase() *TransactionDatabase {
return &TransactionDatabase{
transactions: make(map[int]*Transaction),
}
}
func (transactionDatabase *TransactionDatabase) StoreTransaction(transaction *Transaction) {
transactionDatabase.mutex.Lock()
defer transactionDatabase.mutex.Unlock()
transactionDatabase.transactions[transaction.GetID()] = transaction
}
func (transactionDatabase *TransactionDatabase) LoadTransaction(id int) *Transaction {
transactionDatabase.mutex.RLock()
defer transactionDatabase.mutex.RUnlock()
return transactionDatabase.transactions[id]
}
package social_consensus
type TransactionMetadata struct {
solid bool
transactionLocallyLiked bool
realityLocallyLiked bool
reality int
}
func NewTransactionMetadata() *TransactionMetadata {
return &TransactionMetadata{}
}
func (transactionMetadata *TransactionMetadata) SetSolid(solid bool) {
transactionMetadata.solid = solid
}
func (transactionMetadata *TransactionMetadata) IsSolid() bool {
return transactionMetadata.solid
}
func (transactionMetadata *TransactionMetadata) SetTransactionLocallyLiked(liked bool) {
transactionMetadata.transactionLocallyLiked = liked
}
func (transactionMetadata *TransactionMetadata) IsTransactionLocallyLiked() bool {
return transactionMetadata.transactionLocallyLiked
}
func (transactionMetadata *TransactionMetadata) SetRealityLocallyLiked(liked bool) {
transactionMetadata.realityLocallyLiked = liked
}
func (transactionMetadata *TransactionMetadata) IsRealityLocallyLiked() bool {
return transactionMetadata.realityLocallyLiked
}
func (transactionMetadata *TransactionMetadata) SetReality(reality int) {
transactionMetadata.reality = reality
}
func (transactionMetadata *TransactionMetadata) GetReality() int {
return transactionMetadata.reality
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment