From 1eae8dbcdfb56fecea20df44543f132cd5bc64ec Mon Sep 17 00:00:00 2001 From: Hans Moog <hm@mkjc.net> Date: Thu, 29 Aug 2019 22:35:14 +0200 Subject: [PATCH] Feat: CA consensus nearly finished *ticktock* --- packages/ca/constants.go | 5 + packages/ca/errors.go | 11 ++ packages/ca/heartbeat/heartbeat.go | 128 ++++++++++++++ packages/ca/heartbeat/opinion_statement.go | 167 ++++++++++++++++++ packages/ca/heartbeat/proto/heartbeat.pb.go | 30 ++-- packages/ca/heartbeat/proto/heartbeat.proto | 4 +- .../heartbeat/proto/opinion_statement.pb.go | 46 +++-- .../heartbeat/proto/opinion_statement.proto | 11 +- packages/ca/heartbeat/proto/toggle_type.pb.go | 62 ------- packages/ca/heartbeat/proto/toggle_type.proto | 8 - .../heartbeat/proto/toggled_transaction.pb.go | 38 ++-- .../heartbeat/proto/toggled_transaction.proto | 7 +- packages/ca/heartbeat/serialization_test.go | 5 +- packages/ca/heartbeat/toggled_transaction.go | 91 ++++++++++ packages/ca/heartbeat_manager.go | 111 ++++++++++++ packages/ca/heartbeat_manager_options.go | 26 +++ packages/ca/heartbeat_manager_test.go | 26 +++ packages/ca/neighbor_manager.go | 97 ++++++++++ packages/ca/statement_chain.go | 39 ++++ packages/marshaling/errors.go | 8 + packages/marshaling/marshal.go | 16 ++ packages/marshaling/types.go | 8 + packages/marshaling/unmarshal.go | 17 ++ 23 files changed, 829 insertions(+), 132 deletions(-) create mode 100644 packages/ca/constants.go create mode 100644 packages/ca/errors.go create mode 100644 packages/ca/heartbeat/heartbeat.go create mode 100644 packages/ca/heartbeat/opinion_statement.go delete mode 100644 packages/ca/heartbeat/proto/toggle_type.pb.go delete mode 100644 packages/ca/heartbeat/proto/toggle_type.proto create mode 100644 packages/ca/heartbeat/toggled_transaction.go create mode 100644 packages/ca/heartbeat_manager.go create mode 100644 packages/ca/heartbeat_manager_options.go create mode 100644 packages/ca/heartbeat_manager_test.go create mode 100644 packages/ca/neighbor_manager.go create mode 100644 packages/ca/statement_chain.go create mode 100644 packages/marshaling/errors.go create mode 100644 packages/marshaling/marshal.go create mode 100644 packages/marshaling/types.go create mode 100644 packages/marshaling/unmarshal.go diff --git a/packages/ca/constants.go b/packages/ca/constants.go new file mode 100644 index 00000000..c770ef64 --- /dev/null +++ b/packages/ca/constants.go @@ -0,0 +1,5 @@ +package ca + +const ( + MAX_STATEMENT_TIMEOUT = uint64(5000) +) diff --git a/packages/ca/errors.go b/packages/ca/errors.go new file mode 100644 index 00000000..2fbec995 --- /dev/null +++ b/packages/ca/errors.go @@ -0,0 +1,11 @@ +package ca + +import ( + "github.com/iotaledger/goshimmer/packages/errors" +) + +var ( + ErrMalformedHeartbeat = errors.New("malformed heartbeat") + ErrUnknownNeighbor = errors.New("unknown neighbor") + ErrTooManyNeighbors = errors.New("too many neighbors") +) diff --git a/packages/ca/heartbeat/heartbeat.go b/packages/ca/heartbeat/heartbeat.go new file mode 100644 index 00000000..6087afd8 --- /dev/null +++ b/packages/ca/heartbeat/heartbeat.go @@ -0,0 +1,128 @@ +package heartbeat + +import ( + "sync" + + "github.com/iotaledger/goshimmer/packages/errors" + "github.com/iotaledger/goshimmer/packages/marshaling" + + "github.com/golang/protobuf/proto" + heartbeatProto "github.com/iotaledger/goshimmer/packages/ca/heartbeat/proto" +) + +type Heartbeat struct { + nodeId string + mainStatement *OpinionStatement + neighborStatements map[string]*OpinionStatement + signature []byte + + nodeIdMutex sync.RWMutex + mainStatementMutex sync.RWMutex + neighborStatementsMutex sync.RWMutex + signatureMutex sync.RWMutex +} + +func NewHeartbeat() *Heartbeat { + return &Heartbeat{} +} + +func (heartbeat *Heartbeat) GetNodeId() string { + heartbeat.nodeIdMutex.RLock() + defer heartbeat.nodeIdMutex.RLock() + + return heartbeat.nodeId +} + +func (heartbeat *Heartbeat) SetNodeId(nodeId string) { + heartbeat.nodeIdMutex.Lock() + defer heartbeat.nodeIdMutex.Unlock() + + heartbeat.nodeId = nodeId +} + +func (heartbeat *Heartbeat) GetMainStatement() *OpinionStatement { + heartbeat.mainStatementMutex.RLock() + defer heartbeat.mainStatementMutex.RUnlock() + + return heartbeat.mainStatement +} + +func (heartbeat *Heartbeat) SetMainStatement(mainStatement *OpinionStatement) { + heartbeat.mainStatementMutex.Lock() + defer heartbeat.mainStatementMutex.Unlock() + + heartbeat.mainStatement = mainStatement +} + +func (heartbeat *Heartbeat) GetNeighborStatements() map[string]*OpinionStatement { + heartbeat.neighborStatementsMutex.RLock() + defer heartbeat.neighborStatementsMutex.RUnlock() + + return heartbeat.neighborStatements +} + +func (heartbeat *Heartbeat) SetNeighborStatements(neighborStatements map[string]*OpinionStatement) { + heartbeat.neighborStatementsMutex.Lock() + defer heartbeat.neighborStatementsMutex.Unlock() + + heartbeat.neighborStatements = neighborStatements +} + +func (heartbeat *Heartbeat) GetSignature() []byte { + heartbeat.signatureMutex.RLock() + defer heartbeat.signatureMutex.RUnlock() + + return heartbeat.signature +} + +func (heartbeat *Heartbeat) SetSignature(signature []byte) { + heartbeat.signatureMutex.Lock() + defer heartbeat.signatureMutex.Unlock() + + heartbeat.signature = signature +} + +func (heartbeat *Heartbeat) FromProto(proto proto.Message) { + protoHeartbeat := proto.(*heartbeatProto.HeartBeat) + + var mainStatement OpinionStatement + mainStatement.FromProto(protoHeartbeat.MainStatement) + + neighborStatements := make(map[string]*OpinionStatement, len(protoHeartbeat.NeighborStatements)) + for _, neighborStatement := range protoHeartbeat.NeighborStatements { + var newNeighborStatement OpinionStatement + newNeighborStatement.FromProto(neighborStatement) + + neighborStatements[neighborStatement.NodeId] = &newNeighborStatement + } + + heartbeat.nodeId = protoHeartbeat.NodeId + heartbeat.mainStatement = &mainStatement + heartbeat.neighborStatements = neighborStatements + heartbeat.signature = protoHeartbeat.Signature +} + +func (heartbeat *Heartbeat) ToProto() proto.Message { + neighborStatements := make([]*heartbeatProto.OpinionStatement, len(heartbeat.neighborStatements)) + i := 0 + for _, neighborStatement := range heartbeat.neighborStatements { + neighborStatements[i] = neighborStatement.ToProto().(*heartbeatProto.OpinionStatement) + + i++ + } + + return &heartbeatProto.HeartBeat{ + NodeId: heartbeat.nodeId, + MainStatement: heartbeat.mainStatement.ToProto().(*heartbeatProto.OpinionStatement), + NeighborStatements: neighborStatements, + Signature: heartbeat.signature, + } +} + +func (heartbeat *Heartbeat) MarshalBinary() ([]byte, errors.IdentifiableError) { + return marshaling.Marshal(heartbeat) +} + +func (heartbeat *Heartbeat) UnmarshalBinary(data []byte) (err errors.IdentifiableError) { + return marshaling.Unmarshal(heartbeat, data, &heartbeatProto.HeartBeat{}) +} diff --git a/packages/ca/heartbeat/opinion_statement.go b/packages/ca/heartbeat/opinion_statement.go new file mode 100644 index 00000000..8071114d --- /dev/null +++ b/packages/ca/heartbeat/opinion_statement.go @@ -0,0 +1,167 @@ +package heartbeat + +import ( + "encoding/hex" + "strconv" + "sync" + + "github.com/iotaledger/goshimmer/packages/errors" + "github.com/iotaledger/goshimmer/packages/marshaling" + + "github.com/golang/protobuf/proto" + heartbeatProto "github.com/iotaledger/goshimmer/packages/ca/heartbeat/proto" +) + +type OpinionStatement struct { + previousStatementHash []byte + nodeId string + time uint64 + toggledTransactions []*ToggledTransaction + signature []byte + hash []byte + + previousStatementHashMutex sync.RWMutex + nodeIdMutex sync.RWMutex + timeMutex sync.RWMutex + toggledTransactionsMutex sync.RWMutex + signatureMutex sync.RWMutex + hashMutex sync.RWMutex +} + +func NewOpinionStatement() *OpinionStatement { + return &OpinionStatement{} +} + +func (opinionStatement *OpinionStatement) GetPreviousStatementHash() []byte { + opinionStatement.previousStatementHashMutex.RLock() + defer opinionStatement.previousStatementHashMutex.RUnlock() + + return opinionStatement.previousStatementHash +} + +func (opinionStatement *OpinionStatement) SetPreviousStatementHash(previousStatementHash []byte) { + opinionStatement.previousStatementHashMutex.Lock() + defer opinionStatement.previousStatementHashMutex.Unlock() + + opinionStatement.previousStatementHash = previousStatementHash +} + +func (opinionStatement *OpinionStatement) GetNodeId() string { + opinionStatement.nodeIdMutex.RLock() + defer opinionStatement.nodeIdMutex.RUnlock() + + return opinionStatement.nodeId +} + +func (opinionStatement *OpinionStatement) SetNodeId(nodeId string) { + opinionStatement.nodeIdMutex.Lock() + defer opinionStatement.nodeIdMutex.Unlock() + + opinionStatement.nodeId = nodeId +} + +func (opinionStatement *OpinionStatement) GetTime() uint64 { + opinionStatement.timeMutex.RLock() + defer opinionStatement.timeMutex.RUnlock() + + return opinionStatement.time +} + +func (opinionStatement *OpinionStatement) SetTime(time uint64) { + opinionStatement.timeMutex.Lock() + defer opinionStatement.timeMutex.Unlock() + + opinionStatement.time = time +} + +func (opinionStatement *OpinionStatement) GetToggledTransactions() []*ToggledTransaction { + opinionStatement.toggledTransactionsMutex.RLock() + defer opinionStatement.toggledTransactionsMutex.RUnlock() + + return opinionStatement.toggledTransactions +} + +func (opinionStatement *OpinionStatement) SetToggledTransactions(toggledTransactions []*ToggledTransaction) { + opinionStatement.toggledTransactionsMutex.Lock() + defer opinionStatement.toggledTransactionsMutex.Unlock() + + opinionStatement.toggledTransactions = toggledTransactions +} + +func (opinionStatement *OpinionStatement) GetSignature() []byte { + opinionStatement.signatureMutex.RLock() + defer opinionStatement.signatureMutex.RUnlock() + + return opinionStatement.signature +} + +func (opinionStatement *OpinionStatement) SetSignature(signature []byte) { + opinionStatement.signatureMutex.Lock() + defer opinionStatement.signatureMutex.Unlock() + + opinionStatement.signature = signature +} + +func (opinionStatement *OpinionStatement) GetHash() []byte { + opinionStatement.hashMutex.RLock() + defer opinionStatement.hashMutex.RUnlock() + + return opinionStatement.hash +} + +func (opinionStatement *OpinionStatement) SetHash(hash []byte) { + opinionStatement.hashMutex.Lock() + defer opinionStatement.hashMutex.Unlock() + + opinionStatement.hash = hash +} + +func (opinionStatement *OpinionStatement) FromProto(proto proto.Message) { + protoOpinionStatement := proto.(*heartbeatProto.OpinionStatement) + + opinionStatement.previousStatementHash = protoOpinionStatement.PreviousStatementHash + opinionStatement.nodeId = protoOpinionStatement.NodeId + opinionStatement.time = protoOpinionStatement.Time + opinionStatement.signature = protoOpinionStatement.Signature + + opinionStatement.toggledTransactions = make([]*ToggledTransaction, len(protoOpinionStatement.ToggledTransactions)) + for i, toggledTransaction := range protoOpinionStatement.ToggledTransactions { + var newToggledTransaction ToggledTransaction + newToggledTransaction.FromProto(toggledTransaction) + + opinionStatement.toggledTransactions[i] = &newToggledTransaction + } +} + +func (opinionStatement *OpinionStatement) ToProto() proto.Message { + toggledTransactions := make([]*heartbeatProto.ToggledTransaction, len(opinionStatement.toggledTransactions)) + for i, toggledTransaction := range opinionStatement.toggledTransactions { + toggledTransactions[i] = toggledTransaction.ToProto().(*heartbeatProto.ToggledTransaction) + } + + return &heartbeatProto.OpinionStatement{ + PreviousStatementHash: opinionStatement.previousStatementHash, + NodeId: opinionStatement.nodeId, + Time: opinionStatement.time, + ToggledTransactions: toggledTransactions, + Signature: opinionStatement.signature, + } +} + +func (opinionStatement *OpinionStatement) MarshalBinary() ([]byte, errors.IdentifiableError) { + return marshaling.Marshal(opinionStatement) +} + +func (opinionStatement *OpinionStatement) UnmarshalBinary(data []byte) (err errors.IdentifiableError) { + return marshaling.Unmarshal(opinionStatement, data, &heartbeatProto.OpinionStatement{}) +} + +func (opinionStatement *OpinionStatement) String() string { + return "OpinionStatement {\n" + + " previousStatementHash: 0x" + hex.EncodeToString(opinionStatement.previousStatementHash) + "\n" + + " nodeId: " + opinionStatement.nodeId + "\n" + + " time: " + strconv.Itoa(int(opinionStatement.time)) + "\n" + + " toggledTransactions: [" + "" + "]\n" + + " signature: 0x" + hex.EncodeToString(opinionStatement.signature) + "\n" + + "}" +} diff --git a/packages/ca/heartbeat/proto/heartbeat.pb.go b/packages/ca/heartbeat/proto/heartbeat.pb.go index c18bcd3e..ee8c13d2 100644 --- a/packages/ca/heartbeat/proto/heartbeat.pb.go +++ b/packages/ca/heartbeat/proto/heartbeat.pb.go @@ -21,8 +21,8 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package type HeartBeat struct { - NodeId []byte `protobuf:"bytes,1,opt,name=nodeId,proto3" json:"nodeId,omitempty"` - OwnStatement *OpinionStatement `protobuf:"bytes,2,opt,name=ownStatement,proto3" json:"ownStatement,omitempty"` + NodeId string `protobuf:"bytes,1,opt,name=nodeId,proto3" json:"nodeId,omitempty"` + MainStatement *OpinionStatement `protobuf:"bytes,2,opt,name=mainStatement,proto3" json:"mainStatement,omitempty"` NeighborStatements []*OpinionStatement `protobuf:"bytes,3,rep,name=neighborStatements,proto3" json:"neighborStatements,omitempty"` Signature []byte `protobuf:"bytes,4,opt,name=signature,proto3" json:"signature,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -55,16 +55,16 @@ func (m *HeartBeat) XXX_DiscardUnknown() { var xxx_messageInfo_HeartBeat proto.InternalMessageInfo -func (m *HeartBeat) GetNodeId() []byte { +func (m *HeartBeat) GetNodeId() string { if m != nil { return m.NodeId } - return nil + return "" } -func (m *HeartBeat) GetOwnStatement() *OpinionStatement { +func (m *HeartBeat) GetMainStatement() *OpinionStatement { if m != nil { - return m.OwnStatement + return m.MainStatement } return nil } @@ -90,16 +90,16 @@ func init() { func init() { proto.RegisterFile("heartbeat.proto", fileDescriptor_3c667767fb9826a9) } var fileDescriptor_3c667767fb9826a9 = []byte{ - // 172 bytes of a gzipped FileDescriptorProto + // 176 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcf, 0x48, 0x4d, 0x2c, 0x2a, 0x49, 0x4a, 0x4d, 0x2c, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x52, 0xe2, 0xf9, 0x05, 0x99, 0x79, 0x99, 0xf9, 0x79, 0xf1, 0xc5, 0x25, 0x89, 0x25, 0xa9, 0xb9, 0xa9, - 0x79, 0x50, 0x79, 0xa5, 0xe3, 0x8c, 0x5c, 0x9c, 0x1e, 0x20, 0x3d, 0x4e, 0xa9, 0x89, 0x25, 0x42, - 0x62, 0x5c, 0x6c, 0x79, 0xf9, 0x29, 0xa9, 0x9e, 0x29, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x3c, 0x41, - 0x50, 0x9e, 0x90, 0x35, 0x17, 0x4f, 0x7e, 0x79, 0x5e, 0x30, 0x4c, 0xaf, 0x04, 0x93, 0x02, 0xa3, - 0x06, 0xb7, 0x91, 0x38, 0xc4, 0x0c, 0x3d, 0x7f, 0x88, 0xd9, 0x70, 0xe9, 0x20, 0x14, 0xc5, 0x42, - 0xee, 0x5c, 0x42, 0x79, 0xa9, 0x99, 0xe9, 0x19, 0x49, 0xf9, 0x45, 0x70, 0xc1, 0x62, 0x09, 0x66, - 0x05, 0x66, 0x7c, 0x46, 0x60, 0xd1, 0x22, 0x24, 0xc3, 0xc5, 0x59, 0x9c, 0x99, 0x9e, 0x97, 0x58, - 0x52, 0x5a, 0x94, 0x2a, 0xc1, 0x02, 0x76, 0x20, 0x42, 0x20, 0x89, 0x0d, 0x6c, 0x92, 0x31, 0x20, - 0x00, 0x00, 0xff, 0xff, 0x53, 0x6b, 0x3e, 0x03, 0x03, 0x01, 0x00, 0x00, + 0x79, 0x50, 0x79, 0xa5, 0x93, 0x8c, 0x5c, 0x9c, 0x1e, 0x20, 0x3d, 0x4e, 0xa9, 0x89, 0x25, 0x42, + 0x62, 0x5c, 0x6c, 0x79, 0xf9, 0x29, 0xa9, 0x9e, 0x29, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, + 0x50, 0x9e, 0x90, 0x2d, 0x17, 0x6f, 0x6e, 0x62, 0x66, 0x5e, 0x30, 0x4c, 0xb3, 0x04, 0x93, 0x02, + 0xa3, 0x06, 0xb7, 0x91, 0x38, 0xc4, 0x10, 0x3d, 0x7f, 0x88, 0xe1, 0x70, 0xe9, 0x20, 0x54, 0xd5, + 0x42, 0xee, 0x5c, 0x42, 0x79, 0xa9, 0x99, 0xe9, 0x19, 0x49, 0xf9, 0x45, 0x70, 0xc1, 0x62, 0x09, + 0x66, 0x05, 0x66, 0x7c, 0x66, 0x60, 0xd1, 0x22, 0x24, 0xc3, 0xc5, 0x59, 0x9c, 0x99, 0x9e, 0x97, + 0x58, 0x52, 0x5a, 0x94, 0x2a, 0xc1, 0xa2, 0xc0, 0xa8, 0xc1, 0x13, 0x84, 0x10, 0x48, 0x62, 0x03, + 0x9b, 0x64, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xde, 0x82, 0x59, 0xb6, 0x05, 0x01, 0x00, 0x00, } diff --git a/packages/ca/heartbeat/proto/heartbeat.proto b/packages/ca/heartbeat/proto/heartbeat.proto index 74c49f33..f5a63eb2 100644 --- a/packages/ca/heartbeat/proto/heartbeat.proto +++ b/packages/ca/heartbeat/proto/heartbeat.proto @@ -5,8 +5,8 @@ import "opinion_statement.proto"; package proto; message HeartBeat { - bytes nodeId = 1; - OpinionStatement ownStatement = 2; + string nodeId = 1; + OpinionStatement mainStatement = 2; repeated OpinionStatement neighborStatements = 3; bytes signature = 4; } diff --git a/packages/ca/heartbeat/proto/opinion_statement.pb.go b/packages/ca/heartbeat/proto/opinion_statement.pb.go index 1b71fec0..539aaa09 100644 --- a/packages/ca/heartbeat/proto/opinion_statement.pb.go +++ b/packages/ca/heartbeat/proto/opinion_statement.pb.go @@ -21,13 +21,14 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package type OpinionStatement struct { - NodeId []byte `protobuf:"bytes,1,opt,name=nodeId,proto3" json:"nodeId,omitempty"` - Time uint64 `protobuf:"varint,2,opt,name=time,proto3" json:"time,omitempty"` - ToggledTransactions []*ToggledTransaction `protobuf:"bytes,3,rep,name=toggledTransactions,proto3" json:"toggledTransactions,omitempty"` - Signature []byte `protobuf:"bytes,4,opt,name=signature,proto3" json:"signature,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + PreviousStatementHash []byte `protobuf:"bytes,1,opt,name=previousStatementHash,proto3" json:"previousStatementHash,omitempty"` + NodeId string `protobuf:"bytes,2,opt,name=nodeId,proto3" json:"nodeId,omitempty"` + Time uint64 `protobuf:"varint,3,opt,name=time,proto3" json:"time,omitempty"` + ToggledTransactions []*ToggledTransaction `protobuf:"bytes,4,rep,name=toggledTransactions,proto3" json:"toggledTransactions,omitempty"` + Signature []byte `protobuf:"bytes,5,opt,name=signature,proto3" json:"signature,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *OpinionStatement) Reset() { *m = OpinionStatement{} } @@ -55,13 +56,20 @@ func (m *OpinionStatement) XXX_DiscardUnknown() { var xxx_messageInfo_OpinionStatement proto.InternalMessageInfo -func (m *OpinionStatement) GetNodeId() []byte { +func (m *OpinionStatement) GetPreviousStatementHash() []byte { if m != nil { - return m.NodeId + return m.PreviousStatementHash } return nil } +func (m *OpinionStatement) GetNodeId() string { + if m != nil { + return m.NodeId + } + return "" +} + func (m *OpinionStatement) GetTime() uint64 { if m != nil { return m.Time @@ -90,16 +98,18 @@ func init() { func init() { proto.RegisterFile("opinion_statement.proto", fileDescriptor_a5a0e519e658baf5) } var fileDescriptor_a5a0e519e658baf5 = []byte{ - // 174 bytes of a gzipped FileDescriptorProto + // 202 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xcf, 0x2f, 0xc8, 0xcc, 0xcb, 0xcc, 0xcf, 0x8b, 0x2f, 0x2e, 0x49, 0x2c, 0x49, 0xcd, 0x4d, 0xcd, 0x2b, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x52, 0x92, 0x25, 0xf9, 0xe9, 0xe9, 0x39, 0xa9, 0x29, - 0xf1, 0x25, 0x45, 0x89, 0x79, 0xc5, 0x89, 0xc9, 0x25, 0x99, 0xf9, 0x79, 0x10, 0x15, 0x4a, 0x2b, - 0x19, 0xb9, 0x04, 0xfc, 0x21, 0xba, 0x83, 0x61, 0x9a, 0x85, 0xc4, 0xb8, 0xd8, 0xf2, 0xf2, 0x53, - 0x52, 0x3d, 0x53, 0x24, 0x18, 0x15, 0x18, 0x35, 0x78, 0x82, 0xa0, 0x3c, 0x21, 0x21, 0x2e, 0x96, - 0x92, 0xcc, 0xdc, 0x54, 0x09, 0x26, 0x05, 0x46, 0x0d, 0x96, 0x20, 0x30, 0x5b, 0xc8, 0x9b, 0x4b, - 0x18, 0x6a, 0x7a, 0x08, 0xc2, 0xf0, 0x62, 0x09, 0x66, 0x05, 0x66, 0x0d, 0x6e, 0x23, 0x49, 0x88, - 0x2d, 0x7a, 0x21, 0x18, 0x2a, 0x82, 0xb0, 0xe9, 0x12, 0x92, 0xe1, 0xe2, 0x2c, 0xce, 0x4c, 0xcf, - 0x4b, 0x2c, 0x29, 0x2d, 0x4a, 0x95, 0x60, 0x01, 0xdb, 0x8d, 0x10, 0x48, 0x62, 0x03, 0x1b, 0x66, - 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x22, 0xe9, 0x1c, 0x03, 0xef, 0x00, 0x00, 0x00, + 0xf1, 0x25, 0x45, 0x89, 0x79, 0xc5, 0x89, 0xc9, 0x25, 0x99, 0xf9, 0x79, 0x10, 0x15, 0x4a, 0xf7, + 0x19, 0xb9, 0x04, 0xfc, 0x21, 0xba, 0x83, 0x61, 0x9a, 0x85, 0x4c, 0xb8, 0x44, 0x0b, 0x8a, 0x52, + 0xcb, 0x32, 0xf3, 0x4b, 0x8b, 0xe1, 0x82, 0x1e, 0x89, 0xc5, 0x19, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, + 0x3c, 0x41, 0xd8, 0x25, 0x85, 0xc4, 0xb8, 0xd8, 0xf2, 0xf2, 0x53, 0x52, 0x3d, 0x53, 0x24, 0x98, + 0x14, 0x18, 0x35, 0x38, 0x83, 0xa0, 0x3c, 0x21, 0x21, 0x2e, 0x96, 0x92, 0xcc, 0xdc, 0x54, 0x09, + 0x66, 0x05, 0x46, 0x0d, 0x96, 0x20, 0x30, 0x5b, 0xc8, 0x9b, 0x4b, 0x18, 0xea, 0xa6, 0x10, 0x84, + 0x93, 0x8a, 0x25, 0x58, 0x14, 0x98, 0x35, 0xb8, 0x8d, 0x24, 0x21, 0x6e, 0xd3, 0x0b, 0xc1, 0x50, + 0x11, 0x84, 0x4d, 0x97, 0x90, 0x0c, 0x17, 0x67, 0x71, 0x66, 0x7a, 0x5e, 0x62, 0x49, 0x69, 0x51, + 0xaa, 0x04, 0x2b, 0xd8, 0x89, 0x08, 0x81, 0x24, 0x36, 0xb0, 0x61, 0xc6, 0x80, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xc0, 0x51, 0x41, 0x73, 0x25, 0x01, 0x00, 0x00, } diff --git a/packages/ca/heartbeat/proto/opinion_statement.proto b/packages/ca/heartbeat/proto/opinion_statement.proto index c3d24b70..8f00edcb 100644 --- a/packages/ca/heartbeat/proto/opinion_statement.proto +++ b/packages/ca/heartbeat/proto/opinion_statement.proto @@ -5,8 +5,9 @@ import "toggled_transaction.proto"; package proto; message OpinionStatement { - bytes nodeId = 1; - uint64 time = 2; - repeated ToggledTransaction toggledTransactions = 3; - bytes signature = 4; -}; \ No newline at end of file + bytes previousStatementHash = 1; + string nodeId = 2; + uint64 time = 3; + repeated ToggledTransaction toggledTransactions = 4; + bytes signature = 5; +} \ No newline at end of file diff --git a/packages/ca/heartbeat/proto/toggle_type.pb.go b/packages/ca/heartbeat/proto/toggle_type.pb.go deleted file mode 100644 index bb9e8534..00000000 --- a/packages/ca/heartbeat/proto/toggle_type.pb.go +++ /dev/null @@ -1,62 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: toggle_type.proto - -package proto - -import ( - fmt "fmt" - proto "github.com/golang/protobuf/proto" - math "math" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package - -type ToggleType int32 - -const ( - ToggleType_Like ToggleType = 0 - ToggleType_Dislike ToggleType = 1 -) - -var ToggleType_name = map[int32]string{ - 0: "Like", - 1: "Dislike", -} - -var ToggleType_value = map[string]int32{ - "Like": 0, - "Dislike": 1, -} - -func (x ToggleType) String() string { - return proto.EnumName(ToggleType_name, int32(x)) -} - -func (ToggleType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_f8d4c81885a98bf0, []int{0} -} - -func init() { - proto.RegisterEnum("proto.ToggleType", ToggleType_name, ToggleType_value) -} - -func init() { proto.RegisterFile("toggle_type.proto", fileDescriptor_f8d4c81885a98bf0) } - -var fileDescriptor_f8d4c81885a98bf0 = []byte{ - // 83 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2c, 0xc9, 0x4f, 0x4f, - 0xcf, 0x49, 0x8d, 0x2f, 0xa9, 0x2c, 0x48, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, - 0x53, 0x5a, 0xca, 0x5c, 0x5c, 0x21, 0x60, 0xb9, 0x90, 0xca, 0x82, 0x54, 0x21, 0x0e, 0x2e, 0x16, - 0x9f, 0xcc, 0xec, 0x54, 0x01, 0x06, 0x21, 0x6e, 0x2e, 0x76, 0x97, 0xcc, 0xe2, 0x1c, 0x10, 0x87, - 0x31, 0x89, 0x0d, 0xac, 0xd6, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0xad, 0x5e, 0x8a, 0xc7, 0x47, - 0x00, 0x00, 0x00, -} diff --git a/packages/ca/heartbeat/proto/toggle_type.proto b/packages/ca/heartbeat/proto/toggle_type.proto deleted file mode 100644 index 51435c99..00000000 --- a/packages/ca/heartbeat/proto/toggle_type.proto +++ /dev/null @@ -1,8 +0,0 @@ -syntax = "proto3"; - -package proto; - -enum ToggleType { - Like = 0; - Dislike = 1; -} \ No newline at end of file diff --git a/packages/ca/heartbeat/proto/toggled_transaction.pb.go b/packages/ca/heartbeat/proto/toggled_transaction.pb.go index 593cbeae..2261bd8a 100644 --- a/packages/ca/heartbeat/proto/toggled_transaction.pb.go +++ b/packages/ca/heartbeat/proto/toggled_transaction.pb.go @@ -21,11 +21,12 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package type ToggledTransaction struct { - TransactionId []byte `protobuf:"bytes,1,opt,name=transactionId,proto3" json:"transactionId,omitempty"` - ToggleReason ToggleType `protobuf:"varint,2,opt,name=toggleReason,proto3,enum=proto.ToggleType" json:"toggleReason,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + TransactionId []byte `protobuf:"bytes,1,opt,name=transactionId,proto3" json:"transactionId,omitempty"` + InitialStatement bool `protobuf:"varint,2,opt,name=initialStatement,proto3" json:"initialStatement,omitempty"` + FinalStatement bool `protobuf:"varint,3,opt,name=finalStatement,proto3" json:"finalStatement,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ToggledTransaction) Reset() { *m = ToggledTransaction{} } @@ -60,11 +61,18 @@ func (m *ToggledTransaction) GetTransactionId() []byte { return nil } -func (m *ToggledTransaction) GetToggleReason() ToggleType { +func (m *ToggledTransaction) GetInitialStatement() bool { if m != nil { - return m.ToggleReason + return m.InitialStatement } - return ToggleType_Like + return false +} + +func (m *ToggledTransaction) GetFinalStatement() bool { + if m != nil { + return m.FinalStatement + } + return false } func init() { @@ -74,14 +82,14 @@ func init() { func init() { proto.RegisterFile("toggled_transaction.proto", fileDescriptor_ddb0b50608ac9f9d) } var fileDescriptor_ddb0b50608ac9f9d = []byte{ - // 130 bytes of a gzipped FileDescriptorProto + // 142 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x2c, 0xc9, 0x4f, 0x4f, 0xcf, 0x49, 0x4d, 0x89, 0x2f, 0x29, 0x4a, 0xcc, 0x2b, 0x4e, 0x4c, 0x2e, 0xc9, 0xcc, 0xcf, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x52, 0x82, 0x10, 0x15, 0xf1, 0x25, 0x95, - 0x05, 0xa9, 0x10, 0x19, 0xa5, 0x42, 0x2e, 0xa1, 0x10, 0x88, 0xb6, 0x10, 0x84, 0x2e, 0x21, 0x15, - 0x2e, 0x5e, 0x24, 0x43, 0x3c, 0x53, 0x24, 0x18, 0x15, 0x18, 0x35, 0x78, 0x82, 0x50, 0x05, 0x85, - 0x4c, 0xb9, 0x78, 0x20, 0x06, 0x06, 0xa5, 0x26, 0x16, 0xe7, 0xe7, 0x49, 0x30, 0x29, 0x30, 0x6a, - 0xf0, 0x19, 0x09, 0x42, 0x4c, 0xd6, 0x83, 0x18, 0x1b, 0x52, 0x59, 0x90, 0x1a, 0x84, 0xa2, 0x2c, - 0x89, 0x0d, 0x2c, 0x6f, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xed, 0xb6, 0x2b, 0x6a, 0xb0, 0x00, - 0x00, 0x00, + 0x05, 0xa9, 0x10, 0x19, 0xa5, 0x3e, 0x46, 0x2e, 0xa1, 0x10, 0x88, 0xbe, 0x10, 0x84, 0x36, 0x21, + 0x15, 0x2e, 0x5e, 0x24, 0x53, 0x3c, 0x53, 0x24, 0x18, 0x15, 0x18, 0x35, 0x78, 0x82, 0x50, 0x05, + 0x85, 0xb4, 0xb8, 0x04, 0x32, 0xf3, 0x32, 0x4b, 0x32, 0x13, 0x73, 0x82, 0x4b, 0x12, 0x4b, 0x52, + 0x73, 0x53, 0xf3, 0x4a, 0x24, 0x98, 0x14, 0x18, 0x35, 0x38, 0x82, 0x30, 0xc4, 0x85, 0xd4, 0xb8, + 0xf8, 0xd2, 0x32, 0xf3, 0x90, 0x55, 0x32, 0x83, 0x55, 0xa2, 0x89, 0x26, 0xb1, 0x81, 0xdd, 0x65, + 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x84, 0x60, 0x33, 0xd0, 0xce, 0x00, 0x00, 0x00, } diff --git a/packages/ca/heartbeat/proto/toggled_transaction.proto b/packages/ca/heartbeat/proto/toggled_transaction.proto index 99558098..4439d274 100644 --- a/packages/ca/heartbeat/proto/toggled_transaction.proto +++ b/packages/ca/heartbeat/proto/toggled_transaction.proto @@ -1,10 +1,9 @@ syntax = "proto3"; -import "toggle_type.proto"; - package proto; message ToggledTransaction { bytes transactionId = 1; - ToggleType toggleReason = 2; -}; \ No newline at end of file + bool initialStatement = 2; + bool finalStatement = 3; +} \ No newline at end of file diff --git a/packages/ca/heartbeat/serialization_test.go b/packages/ca/heartbeat/serialization_test.go index c98c532b..89098aa7 100644 --- a/packages/ca/heartbeat/serialization_test.go +++ b/packages/ca/heartbeat/serialization_test.go @@ -13,14 +13,13 @@ import ( ) func TestMarshal(t *testing.T) { - ownNodeId := identity.GenerateRandomIdentity().Identifier + ownNodeId := identity.GenerateRandomIdentity().StringIdentifier toggledTransactions := make([]*heartbeatproto.ToggledTransaction, 10000) for i := 0; i < len(toggledTransactions); i++ { toggledTransactions[i] = &heartbeatproto.ToggledTransaction{ TransactionId: make([]byte, 3), - ToggleReason: 0, } } @@ -43,7 +42,7 @@ func TestMarshal(t *testing.T) { heartbeat := &heartbeatproto.HeartBeat{ NodeId: ownNodeId, - OwnStatement: ownStatement, + MainStatement: ownStatement, NeighborStatements: neighborStatements, Signature: make([]byte, 32), } diff --git a/packages/ca/heartbeat/toggled_transaction.go b/packages/ca/heartbeat/toggled_transaction.go new file mode 100644 index 00000000..a6fb4bb9 --- /dev/null +++ b/packages/ca/heartbeat/toggled_transaction.go @@ -0,0 +1,91 @@ +package heartbeat + +import ( + "sync" + + "github.com/iotaledger/goshimmer/packages/errors" + "github.com/iotaledger/goshimmer/packages/marshaling" + + "github.com/golang/protobuf/proto" + heartbeatProto "github.com/iotaledger/goshimmer/packages/ca/heartbeat/proto" +) + +type ToggledTransaction struct { + transactionId []byte + initialStatement bool + finalStatement bool + + transactionIdMutex sync.RWMutex + initialStatementMutex sync.RWMutex + finalStatementMutex sync.RWMutex +} + +func NewToggledTransaction() *ToggledTransaction { + return &ToggledTransaction{} +} + +func (toggledTransaction *ToggledTransaction) GetTransactionId() []byte { + toggledTransaction.transactionIdMutex.RLock() + defer toggledTransaction.transactionIdMutex.RUnlock() + + return toggledTransaction.transactionId +} + +func (toggledTransaction *ToggledTransaction) SetTransactionId(transactionId []byte) { + toggledTransaction.transactionIdMutex.Lock() + defer toggledTransaction.transactionIdMutex.Unlock() + + toggledTransaction.transactionId = transactionId +} + +func (toggledTransaction *ToggledTransaction) IsInitialStatement() bool { + toggledTransaction.initialStatementMutex.RLock() + defer toggledTransaction.initialStatementMutex.RUnlock() + + return toggledTransaction.initialStatement +} + +func (toggledTransaction *ToggledTransaction) SetInitialStatement(initialStatement bool) { + toggledTransaction.initialStatementMutex.Lock() + defer toggledTransaction.initialStatementMutex.Unlock() + + toggledTransaction.initialStatement = initialStatement +} + +func (toggledTransaction *ToggledTransaction) IsFinalStatement() bool { + toggledTransaction.finalStatementMutex.RLock() + defer toggledTransaction.finalStatementMutex.RUnlock() + + return toggledTransaction.finalStatement +} + +func (toggledTransaction *ToggledTransaction) SetFinalStatement(finalStatement bool) { + toggledTransaction.finalStatementMutex.Lock() + defer toggledTransaction.finalStatementMutex.Unlock() + + toggledTransaction.finalStatement = finalStatement +} + +func (toggledTransaction *ToggledTransaction) FromProto(proto proto.Message) { + protoToggledTransaction := proto.(*heartbeatProto.ToggledTransaction) + + toggledTransaction.transactionId = protoToggledTransaction.TransactionId + toggledTransaction.initialStatement = protoToggledTransaction.InitialStatement + toggledTransaction.finalStatement = protoToggledTransaction.FinalStatement +} + +func (toggledTransaction *ToggledTransaction) ToProto() proto.Message { + return &heartbeatProto.ToggledTransaction{ + TransactionId: toggledTransaction.transactionId, + InitialStatement: toggledTransaction.initialStatement, + FinalStatement: toggledTransaction.finalStatement, + } +} + +func (toggledTransaction *ToggledTransaction) MarshalBinary() ([]byte, errors.IdentifiableError) { + return marshaling.Marshal(toggledTransaction) +} + +func (toggledTransaction *ToggledTransaction) UnmarshalBinary(data []byte) (err errors.IdentifiableError) { + return marshaling.Unmarshal(toggledTransaction, data, &heartbeatProto.ToggledTransaction{}) +} diff --git a/packages/ca/heartbeat_manager.go b/packages/ca/heartbeat_manager.go new file mode 100644 index 00000000..a1d8d64e --- /dev/null +++ b/packages/ca/heartbeat_manager.go @@ -0,0 +1,111 @@ +package ca + +import ( + "sync" + "time" + + "github.com/iotaledger/goshimmer/packages/identity" + + "github.com/iotaledger/goshimmer/packages/ca/heartbeat" + + "github.com/iotaledger/goshimmer/packages/errors" +) + +type HeartbeatManager struct { + identity *identity.Identity + options *HeartbeatManagerOptions + statementChain *StatementChain + neighborManagers map[string]*NeighborManager + initialOpinions map[string][]byte + + neighborManagersMutex sync.RWMutex +} + +func NewHeartbeatManager(identity *identity.Identity, options ...HeartbeatManagerOption) *HeartbeatManager { + return &HeartbeatManager{ + identity: identity, + options: DEFAULT_OPTIONS.Override(options...), + + statementChain: NewStatementChain(), + neighborManagers: make(map[string]*NeighborManager), + initialOpinions: make(map[string][]byte), + } +} + +func (heartbeatManager *HeartbeatManager) SetInitialOpinion(transactionId []byte) { + heartbeatManager.initialOpinions[string(transactionId)] = transactionId +} + +func (heartbeatManager *HeartbeatManager) GenerateMainStatement() (result *heartbeat.OpinionStatement, err errors.IdentifiableError) { + toggledTransactions := make([]*heartbeat.ToggledTransaction, 0) + for _, transactionId := range heartbeatManager.initialOpinions { + newToggledTransaction := heartbeat.NewToggledTransaction() + newToggledTransaction.SetInitialStatement(true) + newToggledTransaction.SetFinalStatement(false) + newToggledTransaction.SetTransactionId(transactionId) + + toggledTransactions = append(toggledTransactions, newToggledTransaction) + } + + mainStatement := heartbeat.NewOpinionStatement() + mainStatement.SetNodeId(heartbeatManager.identity.StringIdentifier) + mainStatement.SetTime(uint64(time.Now().Unix())) + mainStatement.SetToggledTransactions(toggledTransactions) + + if lastAppliedStatement := heartbeatManager.statementChain.lastAppliedStatement; lastAppliedStatement != nil { + mainStatement.SetPreviousStatementHash(lastAppliedStatement.GetHash()) + } + + marshaledStatement, marshalErr := mainStatement.MarshalBinary() + if marshalErr != nil { + err = marshalErr + + return + } + + signature, signingErr := heartbeatManager.identity.Sign(marshaledStatement) + if signingErr != nil { + err = ErrMalformedHeartbeat.Derive(signingErr.Error()) + } + + mainStatement.SetSignature(signature) + + result = mainStatement + + return +} + +func (heartbeatManager *HeartbeatManager) GenerateHeartbeat() (result *heartbeat.Heartbeat, err errors.IdentifiableError) { + mainStatement, mainStatementErr := heartbeatManager.GenerateMainStatement() + if mainStatementErr != nil { + err = mainStatementErr + + return + } + + generatedHeartbeat := heartbeat.NewHeartbeat() + generatedHeartbeat.SetNodeId(heartbeatManager.identity.StringIdentifier) + generatedHeartbeat.SetMainStatement(mainStatement) + generatedHeartbeat.SetNeighborStatements(nil) + generatedHeartbeat.SetSignature(nil) + + result = generatedHeartbeat + + return +} + +func (heartbeatManager *HeartbeatManager) ApplyHeartbeat(heartbeat *heartbeat.Heartbeat) (err errors.IdentifiableError) { + heartbeatManager.neighborManagersMutex.RLock() + defer heartbeatManager.neighborManagersMutex.RUnlock() + + issuerId := heartbeat.GetNodeId() + + neighborManager, neighborExists := heartbeatManager.neighborManagers[issuerId] + if !neighborExists { + err = ErrUnknownNeighbor.Derive("unknown neighbor: " + issuerId) + } else { + err = neighborManager.ApplyHeartbeat(heartbeat) + } + + return +} diff --git a/packages/ca/heartbeat_manager_options.go b/packages/ca/heartbeat_manager_options.go new file mode 100644 index 00000000..af507e47 --- /dev/null +++ b/packages/ca/heartbeat_manager_options.go @@ -0,0 +1,26 @@ +package ca + +type HeartbeatManagerOptions struct { + maxNeighborChains int +} + +func (options HeartbeatManagerOptions) Override(optionalOptions ...HeartbeatManagerOption) *HeartbeatManagerOptions { + result := &options + for _, option := range optionalOptions { + option(result) + } + + return result +} + +type HeartbeatManagerOption func(*HeartbeatManagerOptions) + +func MaxNeighborChains(maxNeighborChains int) HeartbeatManagerOption { + return func(args *HeartbeatManagerOptions) { + args.maxNeighborChains = maxNeighborChains + } +} + +var DEFAULT_OPTIONS = &HeartbeatManagerOptions{ + maxNeighborChains: 8, +} diff --git a/packages/ca/heartbeat_manager_test.go b/packages/ca/heartbeat_manager_test.go new file mode 100644 index 00000000..9c5bc1a5 --- /dev/null +++ b/packages/ca/heartbeat_manager_test.go @@ -0,0 +1,26 @@ +package ca + +import ( + "crypto/rand" + "fmt" + "testing" + + "github.com/iotaledger/goshimmer/packages/identity" +) + +func TestHeartbeatManager_GenerateHeartbeat(t *testing.T) { + transactionId1 := make([]byte, 50) + rand.Read(transactionId1) + + heartbeatManager := NewHeartbeatManager(identity.GenerateRandomIdentity()) + heartbeatManager.SetInitialOpinion(transactionId1) + + result, err := heartbeatManager.GenerateHeartbeat() + if err != nil { + t.Error(err) + + return + } + + fmt.Println(result.GetMainStatement()) +} diff --git a/packages/ca/neighbor_manager.go b/packages/ca/neighbor_manager.go new file mode 100644 index 00000000..b9a03aa6 --- /dev/null +++ b/packages/ca/neighbor_manager.go @@ -0,0 +1,97 @@ +package ca + +import ( + "bytes" + "strconv" + + "github.com/iotaledger/goshimmer/packages/ca/heartbeat" + + "github.com/iotaledger/goshimmer/packages/errors" +) + +type NeighborManager struct { + options *NeighborManagerOptions + mainChain *StatementChain + neighborChains map[string]*StatementChain +} + +func NewNeighborManager(options ...NeighborManagerOption) *NeighborManager { + return &NeighborManager{ + options: (&NeighborManagerOptions{}).Override(options...), + mainChain: NewStatementChain(), + neighborChains: make(map[string]*StatementChain), + } +} + +func (neighborManager *NeighborManager) Reset() { + neighborManager.mainChain.Reset() + neighborManager.neighborChains = make(map[string]*StatementChain) +} + +func (neighborManager *NeighborManager) ApplyHeartbeat(heartbeat *heartbeat.Heartbeat) (err errors.IdentifiableError) { + // region check if heartbeat is "syntactically correct" //////////////////////////////////////////////////////////// + + mainStatement := heartbeat.GetMainStatement() + if mainStatement == nil { + return ErrMalformedHeartbeat.Derive("missing main statement in heartbeat") + } + + neighborStatements := heartbeat.GetNeighborStatements() + if len(neighborStatements) > neighborManager.options.maxNeighborChains { + return ErrTooManyNeighbors.Derive("too many neighbors in statement of " + heartbeat.GetNodeId() + ": " + strconv.Itoa(len(neighborStatements))) + } + + // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////// + + // region check if heartbeat is semantically correct /////////////////////////////////////////////////////////////// + + // check if referenced main statement is missing + if previousStatementHash := mainStatement.GetPreviousStatementHash(); len(previousStatementHash) != 0 { + lastAppliedMainStatement := neighborManager.mainChain.GetLastAppliedStatement() + if lastAppliedMainStatement != nil && !bytes.Equal(lastAppliedMainStatement.GetHash(), previousStatementHash) { + if (mainStatement.GetTime() - lastAppliedMainStatement.GetTime()) < MAX_STATEMENT_TIMEOUT { + // request missing heartbeat + neighborManager.mainChain.AddStatement(mainStatement) + + return + } else { + neighborManager.Reset() + } + } + } else { + neighborManager.mainChain.AddStatement(mainStatement) + } + + // check if referenced neighbor statements are missing + for neighborId, neighborStatement := range neighborStatements { + neighborChain, exists := neighborManager.neighborChains[neighborId] + if exists { + lastAppliedNeighborStatement := neighborChain.GetLastAppliedStatement() + if lastAppliedNeighborStatement != nil && !bytes.Equal(lastAppliedNeighborStatement.GetHash(), neighborStatement.GetPreviousStatementHash()) { + return ErrMalformedHeartbeat.Derive("missing neighbor statement") + } + } + } + + // check if neighbor statements are a valid justification for main statement + // sthsth + + // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////// + + return +} + +type NeighborManagerOptions struct { + maxNeighborChains int +} + +func (options NeighborManagerOptions) Override(optionalOptions ...NeighborManagerOption) *NeighborManagerOptions { + result := &options + for _, option := range optionalOptions { + option(result) + } + + return result +} + +type NeighborManagerOption func(*NeighborManagerOptions) diff --git a/packages/ca/statement_chain.go b/packages/ca/statement_chain.go new file mode 100644 index 00000000..7333bc40 --- /dev/null +++ b/packages/ca/statement_chain.go @@ -0,0 +1,39 @@ +package ca + +import ( + "github.com/iotaledger/goshimmer/packages/ca/heartbeat" + "github.com/iotaledger/goshimmer/packages/errors" +) + +type StatementChain struct { + lastAppliedStatement *heartbeat.OpinionStatement + lastReceivedStatement *heartbeat.OpinionStatement +} + +func NewStatementChain() *StatementChain { + return &StatementChain{} +} + +func (statementChain *StatementChain) AddStatement(statement *heartbeat.OpinionStatement) { + +} + +func (statementChain *StatementChain) Reset() { + +} + +func (statementChain *StatementChain) Apply(statement *heartbeat.OpinionStatement) (err errors.IdentifiableError) { + return +} + +func (statementChain *StatementChain) SetLastReceivedStatement(statement *heartbeat.OpinionStatement) { + statementChain.lastReceivedStatement = statement +} + +func (statementChain *StatementChain) GetLastAppliedStatement() *heartbeat.OpinionStatement { + return statementChain.lastAppliedStatement +} + +func (statementChain *StatementChain) StatementExists(statementHash []byte) bool { + return true +} diff --git a/packages/marshaling/errors.go b/packages/marshaling/errors.go new file mode 100644 index 00000000..73b27016 --- /dev/null +++ b/packages/marshaling/errors.go @@ -0,0 +1,8 @@ +package marshaling + +import "github.com/iotaledger/goshimmer/packages/errors" + +var ( + ErrUnmarshalFailed = errors.Wrap(errors.New("unmarshal failed"), "unmarshal failed") + ErrMarshalFailed = errors.Wrap(errors.New("marshal failed"), "marshal failed") +) diff --git a/packages/marshaling/marshal.go b/packages/marshaling/marshal.go new file mode 100644 index 00000000..04cdf7f2 --- /dev/null +++ b/packages/marshaling/marshal.go @@ -0,0 +1,16 @@ +package marshaling + +import ( + "github.com/golang/protobuf/proto" + "github.com/iotaledger/goshimmer/packages/errors" +) + +func Marshal(source ProtocolBufferTarget) (result []byte, err errors.IdentifiableError) { + if marshaledData, marshalErr := proto.Marshal(source.ToProto()); marshalErr != nil { + err = ErrMarshalFailed.Derive(marshalErr, "marshal failed") + } else { + result = marshaledData + } + + return +} diff --git a/packages/marshaling/types.go b/packages/marshaling/types.go new file mode 100644 index 00000000..0d334681 --- /dev/null +++ b/packages/marshaling/types.go @@ -0,0 +1,8 @@ +package marshaling + +import "github.com/golang/protobuf/proto" + +type ProtocolBufferTarget interface { + ToProto() (result proto.Message) + FromProto(proto proto.Message) +} diff --git a/packages/marshaling/unmarshal.go b/packages/marshaling/unmarshal.go new file mode 100644 index 00000000..fd2d69aa --- /dev/null +++ b/packages/marshaling/unmarshal.go @@ -0,0 +1,17 @@ +package marshaling + +import ( + "github.com/golang/protobuf/proto" + "github.com/iotaledger/goshimmer/packages/errors" +) + +// Unmarshals the given data into the target using the given protobuf type. +func Unmarshal(target ProtocolBufferTarget, data []byte, messageType proto.Message) (err errors.IdentifiableError) { + if unmarshalError := proto.Unmarshal(data, messageType); unmarshalError != nil { + err = ErrUnmarshalFailed.Derive(unmarshalError, "unmarshal failed") + } else { + target.FromProto(messageType) + } + + return +} -- GitLab