diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..efa49079f70407b247a5f9037300b3ca6818145b --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project files +.idea + +# Database directory +mainnetdb/ \ No newline at end of file diff --git a/packages/accountability/accountability.go b/packages/accountability/accountability.go new file mode 100644 index 0000000000000000000000000000000000000000..e581796192e6fd2d5ed4f3462a2f678863cb9886 --- /dev/null +++ b/packages/accountability/accountability.go @@ -0,0 +1,49 @@ +package accountability + +import ( + "github.com/dgraph-io/badger" + "github.com/iotaledger/goshimmer/packages/settings" + "github.com/iotaledger/goshimmer/packages/identity" +) + +var OWN_ID *identity.Identity + +func generateNewIdentity() *identity.Identity { + newIdentity := identity.GenerateRandomIdentity() + + if err := settings.Set([]byte("ACCOUNTABILITY_PUBLIC_KEY"), newIdentity.PublicKey); err != nil { + panic(err) + } + + if err := settings.Set([]byte("ACCOUNTABILITY_PRIVATE_KEY"), newIdentity.PrivateKey); err != nil { + panic(err) + } + + return newIdentity +} + +func getIdentity() *identity.Identity { + publicKey, err := settings.Get([]byte("ACCOUNTABILITY_PUBLIC_KEY")) + if err != nil { + if err == badger.ErrKeyNotFound { + return generateNewIdentity() + } else { + panic(err) + } + } + + privateKey, err := settings.Get([]byte("ACCOUNTABILITY_PRIVATE_KEY")) + if err != nil { + if err == badger.ErrKeyNotFound { + return generateNewIdentity() + } else { + panic(err) + } + } + + return identity.NewIdentity(publicKey, privateKey) +} + +func init() { + OWN_ID = getIdentity() +} \ No newline at end of file diff --git a/packages/daemon/daemon.go b/packages/daemon/daemon.go new file mode 100644 index 0000000000000000000000000000000000000000..46f6822e9961861b0d41c039a4997606ddfd2fcc --- /dev/null +++ b/packages/daemon/daemon.go @@ -0,0 +1,98 @@ +package daemon + +import ( + "sync" +) + +var ( + running bool + runOnce sync.Once + shutdownOnce sync.Once + wg sync.WaitGroup + installWG sync.WaitGroup + ShutdownSignal = make(chan int, 1) + backgroundWorkers = make([]func(), 0) + backgroundWorkerChan = make(chan func(), 10) +) + +var Events = daemonEvents{ + Run: &callbackEvent{ + callbacks: map[uintptr]Callback{}, + }, + Shutdown: &callbackEvent{ + callbacks: map[uintptr]Callback{}, + }, +} + +func init() { + shutdownOnce.Do(func() {}) + + go func() { + for { + backgroundWorker := <- backgroundWorkerChan + + backgroundWorkers = append(backgroundWorkers, backgroundWorker) + + installWG.Done() + + if IsRunning() { + runBackgroundWorker(backgroundWorker) + } + } + }() +} + +func runBackgroundWorker(backgroundWorker func()) { + wg.Add(1) + + go func() { + backgroundWorker() + + wg.Done() + }() +} + +func runBackgroundWorkers() { + for _, backgroundWorker := range backgroundWorkers { + runBackgroundWorker(backgroundWorker) + } +} + +func BackgroundWorker(handler func()) { + installWG.Add(1) + + backgroundWorkerChan <- handler +} + +func Run() { + runOnce.Do(func() { + installWG.Wait() + + ShutdownSignal = make(chan int, 1) + running = true + + Events.Run.Trigger() + + runBackgroundWorkers() + + shutdownOnce = sync.Once{} + }) + + wg.Wait() +} + +func Shutdown() { + shutdownOnce.Do(func() { + close(ShutdownSignal) + + running = false + + Events.Shutdown.Trigger() + + runOnce = sync.Once{} + }) +} + +func IsRunning() bool { + return running +} diff --git a/packages/daemon/events.go b/packages/daemon/events.go new file mode 100644 index 0000000000000000000000000000000000000000..b8cef7a985b6aec434f9c1442f3cd5afe488264b --- /dev/null +++ b/packages/daemon/events.go @@ -0,0 +1,26 @@ +package daemon + +import "reflect" + +type daemonEvents struct { + Run *callbackEvent + Shutdown *callbackEvent +} + +type callbackEvent struct { + callbacks map[uintptr]Callback +} + +func (this *callbackEvent) Attach(callback Callback) { + this.callbacks[reflect.ValueOf(callback).Pointer()] = callback +} + +func (this *callbackEvent) Detach(callback Callback) { + delete(this.callbacks, reflect.ValueOf(callback).Pointer()) +} + +func (this *callbackEvent) Trigger() { + for _, callback := range this.callbacks { + callback() + } +} diff --git a/packages/daemon/types.go b/packages/daemon/types.go new file mode 100644 index 0000000000000000000000000000000000000000..e87a1a750e3f97d9bcb11bb536a694a39ea1b016 --- /dev/null +++ b/packages/daemon/types.go @@ -0,0 +1,3 @@ +package daemon + +type Callback = func() diff --git a/packages/database/database.go b/packages/database/database.go new file mode 100644 index 0000000000000000000000000000000000000000..4dfb0803ef83de32fbf58e55d355ef9c8aea0064 --- /dev/null +++ b/packages/database/database.go @@ -0,0 +1,114 @@ +package database + +import ( + "github.com/dgraph-io/badger" + "os" + "path/filepath" + "sync" +) + +var databasesByName = make(map[string]*databaseImpl) +var getLock sync.Mutex + +var ErrKeyNotFound = badger.ErrKeyNotFound + +type databaseImpl struct { + db *badger.DB + name string + openLock sync.Mutex +} + +func Get(name string) (Database, error) { + getLock.Lock() + defer getLock.Unlock() + + if database, exists := databasesByName[name]; exists { + return database, nil + } + + database := &databaseImpl{ + db: nil, + name: name, + } + if err := database.Open(); err != nil { + return nil, err + } + + databasesByName[name] = database + + return databasesByName[name], nil +} + +func (this *databaseImpl) Open() error { + this.openLock.Lock() + defer this.openLock.Unlock() + + if this.db == nil { + directory := *DIRECTORY.Value + + if _, err := os.Stat(directory); os.IsNotExist(err) { + if err := os.Mkdir(directory, 0700); err != nil { + return err + } + } + + opts := badger.DefaultOptions + opts.Dir = directory + string(filepath.Separator) + this.name + opts.ValueDir = opts.Dir + opts.Logger = &logger{} + opts.Truncate = true + + db, err := badger.Open(opts) + if err != nil { + return err + } + this.db = db + } + + return nil +} + +func (this *databaseImpl) Set(key []byte, value []byte) error { + if err := this.db.Update(func(txn *badger.Txn) error { return txn.Set(key, value) }); err != nil { + return err + } + + return nil +} + +func (this *databaseImpl) Get(key []byte) ([]byte, error) { + var result []byte = nil + var err error = nil + + err = this.db.View(func(txn *badger.Txn) error { + item, err := txn.Get(key) + if err != nil { + return err + } + + return item.Value(func(val []byte) error { + result = append([]byte{}, val...) + + return nil + }) + }) + + return result, err +} + +func (this *databaseImpl) Close() error { + this.openLock.Lock() + defer this.openLock.Unlock() + + if this.db != nil { + err := this.db.Close() + + this.db = nil + + if err != nil { + return err + } + } + + return nil +} diff --git a/packages/database/interfaces.go b/packages/database/interfaces.go new file mode 100644 index 0000000000000000000000000000000000000000..c8fbb8295a3be54c12754d81638e6c85d88aeef2 --- /dev/null +++ b/packages/database/interfaces.go @@ -0,0 +1,8 @@ +package database + +type Database interface { + Open() error + Set(key []byte, value []byte) error + Get(key []byte) ([]byte, error) + Close() error +} diff --git a/packages/database/logger.go b/packages/database/logger.go new file mode 100644 index 0000000000000000000000000000000000000000..e8a4c6fda2f351a48938b6c9d7d3ba2d7136fe39 --- /dev/null +++ b/packages/database/logger.go @@ -0,0 +1,15 @@ +package database + +type logger struct {} + +func (this *logger) Errorf(string, ...interface{}) { + // disable logging +} + +func (this *logger) Infof(string, ...interface{}) { + // disable logging +} + +func (this *logger) Warningf(string, ...interface{}) { + // disable logging +} diff --git a/packages/database/parameters.go b/packages/database/parameters.go new file mode 100644 index 0000000000000000000000000000000000000000..4e28a7793085065ebb704e03673696dad1d1477d --- /dev/null +++ b/packages/database/parameters.go @@ -0,0 +1,7 @@ +package database + +import ( + "github.com/iotadevelopment/go/modules/parameter" +) + +var DIRECTORY = parameter.AddString("DATABASE/DIRECTORY", "mainnetdb", "path to the database folder") diff --git a/packages/identity/constants.go b/packages/identity/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..0d7a5b986b1b57d5a3774256154d08898f7950f3 --- /dev/null +++ b/packages/identity/constants.go @@ -0,0 +1,8 @@ +package identity + +const ( + PRIVATE_TYPE = IdentityType(0) + PUBLIC_TYPE = IdentityType(1) + + PUBLIC_KEY_BYTE_LENGTH = 65 +) diff --git a/packages/identity/identity.go b/packages/identity/identity.go new file mode 100644 index 0000000000000000000000000000000000000000..05815470eb30d85715a6e821b4235cb3b8b58581 --- /dev/null +++ b/packages/identity/identity.go @@ -0,0 +1,86 @@ +package identity + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "fmt" + "github.com/ethereum/go-ethereum/crypto/secp256k1" + "github.com/iotadevelopment/consensus/crypto" +) + +type Identity struct { + Type IdentityType + Identifier []byte + StringIdentifier string + PublicKey []byte + PrivateKey []byte +} + +func NewIdentity(publicKey []byte, optionalPrivateKey ... []byte) *Identity { + this := &Identity{ + Identifier: crypto.Hash20(publicKey), + PublicKey: publicKey, + } + + this.StringIdentifier = fmt.Sprintf("%x", this.Identifier) + + if len(optionalPrivateKey) == 1 { + this.Type = PRIVATE_TYPE + this.PrivateKey = optionalPrivateKey[0] + } else { + this.Type = PUBLIC_TYPE + } + + return this +} + +func (this *Identity) Sign(data []byte) ([]byte, error) { + sha256Hasher := sha256.New() + sha256Hasher.Write(data) + + sig, err := secp256k1.Sign(sha256Hasher.Sum(nil), this.PrivateKey) + if err != nil { + return nil, err + } + + return sig, nil +} + +func (this *Identity) VerifySignature(data []byte, signature []byte) bool { + sha256Hasher := sha256.New() + sha256Hasher.Write(data) + + return secp256k1.VerifySignature(this.PublicKey, sha256Hasher.Sum(nil), signature[:64]) +} + +func GenerateRandomIdentity() *Identity { + // generate key pair + keyPair, err := ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) + if err != nil { + panic(err) + } + + // build public key bytes + publicKey := elliptic.Marshal(secp256k1.S256(), keyPair.X, keyPair.Y) + + // build private key bytes + privkey := make([]byte, 32) + blob := keyPair.D.Bytes() + copy(privkey[32-len(blob):], blob) + + return NewIdentity(publicKey, privkey) +} + +func FromSignedData(data []byte, signature []byte) (*Identity, error) { + sha256Hasher := sha256.New() + sha256Hasher.Write(data) + + pubKey, err := secp256k1.RecoverPubkey(sha256Hasher.Sum(nil), signature) + if err != nil { + return nil, err + } + + return NewIdentity(pubKey), nil +} diff --git a/packages/identity/types.go b/packages/identity/types.go new file mode 100644 index 0000000000000000000000000000000000000000..30565ab6786fc441931203041602e4d47b1e539f --- /dev/null +++ b/packages/identity/types.go @@ -0,0 +1,3 @@ +package identity + +type IdentityType int diff --git a/packages/network/interfaces.go b/packages/network/interfaces.go new file mode 100644 index 0000000000000000000000000000000000000000..7590e112572aab36cbd741a2ed6877a19e18b116 --- /dev/null +++ b/packages/network/interfaces.go @@ -0,0 +1,27 @@ +package network + +import ( + "net" + "time" +) + +type Connection interface { + GetProtocol() string + GetConnection() net.Conn + Write(data []byte) + OnReceiveData(callback DataConsumer) Connection + OnDisconnect(callback Callback) Connection + OnError(callback ErrorConsumer) Connection + TriggerReceiveData(data []byte) Connection + TriggerDisconnect() Connection + TriggerError(err error) Connection + SetTimeout(duration time.Duration) Connection + HandleConnection() +} + +type Callback func() + +type ErrorConsumer func(err error) + +type DataConsumer func(data []byte) + diff --git a/packages/network/parameters.go b/packages/network/parameters.go new file mode 100644 index 0000000000000000000000000000000000000000..65200679dc22d3777e39a8b3cb80613e7cb8f41b --- /dev/null +++ b/packages/network/parameters.go @@ -0,0 +1,5 @@ +package network + +const ( + READ_BUFFER_SIZE = 81920 +) diff --git a/packages/network/peer.go b/packages/network/peer.go new file mode 100644 index 0000000000000000000000000000000000000000..7035a804371be58d3ce1da13aa7361cddcd3062d --- /dev/null +++ b/packages/network/peer.go @@ -0,0 +1,118 @@ +package network + +import ( + "io" + "net" + "time" +) + +type peerImplementation struct { + timeout time.Duration + protocol string + conn net.Conn + receiveDataHandlers []DataConsumer + disconnectHandlers []Callback + errorHandlers []ErrorConsumer +} + +func NewPeer(protocol string, conn net.Conn) Connection { + this := &peerImplementation{ + protocol: protocol, + conn: conn, + receiveDataHandlers: make([]DataConsumer, 0), + disconnectHandlers: make([]Callback, 0), + errorHandlers: make([]ErrorConsumer, 0), + } + + return this +} + +func (this *peerImplementation) SetTimeout(duration time.Duration) Connection { + this.timeout = duration + + //this.conn.SetDeadline(time.Now().Add(this.timeout)) + + return this +} + +func (this *peerImplementation) GetProtocol() string { + return this.protocol +} + +func (this *peerImplementation) GetConnection() net.Conn { + return this.conn +} + +func (this *peerImplementation) Write(data []byte) { + //this.conn.SetDeadline(time.Now().Add(this.timeout)) + + if _, err := this.conn.Write(data); err != nil { + this.TriggerError(err) + } +} + +func (this *peerImplementation) OnReceiveData(callback DataConsumer) Connection { + this.receiveDataHandlers = append(this.receiveDataHandlers, callback) + + return this +} + +func (this *peerImplementation) OnDisconnect(callback Callback) Connection { + this.disconnectHandlers = append(this.disconnectHandlers, callback) + + return this +} + +func (this *peerImplementation) OnError(callback ErrorConsumer) Connection { + this.errorHandlers = append(this.errorHandlers, callback) + + return this +} + +func (this *peerImplementation) TriggerReceiveData(data []byte) Connection { + for _, receiveDataHandler := range this.receiveDataHandlers { + receiveDataHandler(data) + } + + return this +} + +func (this *peerImplementation) TriggerDisconnect() Connection { + for _, disconnectHandler := range this.disconnectHandlers { + disconnectHandler() + } + + return this +} + +func (this *peerImplementation) TriggerError(err error) Connection { + for _, errorHandler := range this.errorHandlers { + errorHandler(err) + } + + return this +} + +func (this *peerImplementation) HandleConnection() { + defer this.conn.Close() + defer this.TriggerDisconnect() + + receiveBuffer := make([]byte, READ_BUFFER_SIZE) + for { + //this.conn.SetDeadline(time.Now().Add(this.timeout)) + + byteCount, err := this.conn.Read(receiveBuffer) + if err != nil { + if err != io.EOF { + this.TriggerError(err) + } + + return + } + + receivedData := make([]byte, byteCount) + copy(receivedData, receiveBuffer) + + this.TriggerReceiveData(receivedData) + } +} diff --git a/packages/network/tcp/server.go b/packages/network/tcp/server.go new file mode 100644 index 0000000000000000000000000000000000000000..51ec4bbab23d0409699941acee3061f254e6afc6 --- /dev/null +++ b/packages/network/tcp/server.go @@ -0,0 +1,60 @@ +package tcp + +import ( + "github.com/iotaledger/goshimmer/packages/network" + "net" + "strconv" +) + +type Server struct { + Socket net.Listener + Events serverEvents +} + +func (this *Server) Shutdown() { + if this.Socket != nil { + socket := this.Socket + this.Socket = nil + + socket.Close() + } +} + +func (this *Server) Listen(port int) *Server { + socket, err := net.Listen("tcp4", "0.0.0.0:"+strconv.Itoa(port)) + if err != nil { + this.Events.Error.Trigger(err) + + return this + } else { + this.Socket = socket + } + + this.Events.Start.Trigger() + defer this.Events.Shutdown.Trigger() + + for this.Socket != nil { + if socket, err := this.Socket.Accept(); err != nil { + if this.Socket != nil { + this.Events.Error.Trigger(err) + } + } else { + peer := network.NewPeer("tcp", socket) + + go this.Events.Connect.Trigger(peer) + } + } + + return this +} + +func NewServer() *Server { + return &Server{ + Events: serverEvents{ + Start: &callbackEvent{make(map[uintptr]Callback)}, + Shutdown: &callbackEvent{make(map[uintptr]Callback)}, + Connect: &peerConsumerEvent{make(map[uintptr]PeerConsumer)}, + Error: &errorConsumerEvent{make(map[uintptr]ErrorConsumer)}, + }, + } +} diff --git a/packages/network/tcp/server_events.go b/packages/network/tcp/server_events.go new file mode 100644 index 0000000000000000000000000000000000000000..27a3af2666bb2f8358bbf0bd08200c33c5ab7f0a --- /dev/null +++ b/packages/network/tcp/server_events.go @@ -0,0 +1,79 @@ +package tcp + +import ( + "github.com/iotaledger/goshimmer/packages/network" + "reflect" +) + +type serverEvents struct { + Start *callbackEvent + Shutdown *callbackEvent + Connect *peerConsumerEvent + Error *errorConsumerEvent +} + +// region callbackEvent ///////////////////////////////////////////////////////////////////////////////////////////////// + +type callbackEvent struct { + callbacks map[uintptr]Callback +} + +func (this *callbackEvent) Attach(callback Callback) { + this.callbacks[reflect.ValueOf(callback).Pointer()] = callback +} + +func (this *callbackEvent) Detach(callback Callback) { + delete(this.callbacks, reflect.ValueOf(callback).Pointer()) +} + +func (this *callbackEvent) Trigger() { + for _, callback := range this.callbacks { + callback() + } +} + +// endregion //////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region errorConsumerEvent //////////////////////////////////////////////////////////////////////////////////////////// + +type errorConsumerEvent struct { + callbacks map[uintptr]ErrorConsumer +} + +func (this *errorConsumerEvent) Attach(callback ErrorConsumer) { + this.callbacks[reflect.ValueOf(callback).Pointer()] = callback +} + +func (this *errorConsumerEvent) Detach(callback ErrorConsumer) { + delete(this.callbacks, reflect.ValueOf(callback).Pointer()) +} + +func (this *errorConsumerEvent) Trigger(err error) { + for _, callback := range this.callbacks { + callback(err) + } +} + +// endregion //////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region peerConsumerEvent ///////////////////////////////////////////////////////////////////////////////////////////// + +type peerConsumerEvent struct { + callbacks map[uintptr]PeerConsumer +} + +func (this *peerConsumerEvent) Attach(callback PeerConsumer) { + this.callbacks[reflect.ValueOf(callback).Pointer()] = callback +} + +func (this *peerConsumerEvent) Detach(callback PeerConsumer) { + delete(this.callbacks, reflect.ValueOf(callback).Pointer()) +} + +func (this *peerConsumerEvent) Trigger(peer network.Connection) { + for _, callback := range this.callbacks { + callback(peer) + } +} + +// endregion //////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/network/tcp/types.go b/packages/network/tcp/types.go new file mode 100644 index 0000000000000000000000000000000000000000..6eb9d5d16eae568e8849a5d4adba75f6016ccb88 --- /dev/null +++ b/packages/network/tcp/types.go @@ -0,0 +1,9 @@ +package tcp + +import "github.com/iotaledger/goshimmer/packages/network" + +type Callback = func() + +type ErrorConsumer = func(e error) + +type PeerConsumer = func(peer network.Connection) diff --git a/packages/network/udp/events.go b/packages/network/udp/events.go new file mode 100644 index 0000000000000000000000000000000000000000..0ce863860fa74063d4df934b8d5f67fe8ca2feb4 --- /dev/null +++ b/packages/network/udp/events.go @@ -0,0 +1,72 @@ +package udp + +import ( + "net" + "reflect" +) + +//region peerConsumerEvent ///////////////////////////////////////////////////////////////////////////////////////////// + +type callbackEvent struct { + callbacks map[uintptr]Callback +} + +func (this *callbackEvent) Attach(callback Callback) { + this.callbacks[reflect.ValueOf(callback).Pointer()] = callback +} + +func (this *callbackEvent) Detach(callback Callback) { + delete(this.callbacks, reflect.ValueOf(callback).Pointer()) +} + +func (this *callbackEvent) Trigger() { + for _, callback := range this.callbacks { + callback() + } +} + +//endregion //////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//region dataConsumerEvent ///////////////////////////////////////////////////////////////////////////////////////////// + +type dataConsumerEvent struct { + callbacks map[uintptr]AddressDataConsumer +} + +func (this *dataConsumerEvent) Attach(callback AddressDataConsumer) { + this.callbacks[reflect.ValueOf(callback).Pointer()] = callback +} + +func (this *dataConsumerEvent) Detach(callback AddressDataConsumer) { + delete(this.callbacks, reflect.ValueOf(callback).Pointer()) +} + +func (this *dataConsumerEvent) Trigger(addr *net.UDPAddr, data []byte) { + for _, callback := range this.callbacks { + callback(addr, data) + } +} + +//endregion //////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//region errorConsumerEvent //////////////////////////////////////////////////////////////////////////////////////////// + +type errorConsumerEvent struct { + callbacks map[uintptr]ErrorConsumer +} + +func (this *errorConsumerEvent) Attach(callback ErrorConsumer) { + this.callbacks[reflect.ValueOf(callback).Pointer()] = callback +} + +func (this *errorConsumerEvent) Detach(callback ErrorConsumer) { + delete(this.callbacks, reflect.ValueOf(callback).Pointer()) +} + +func (this *errorConsumerEvent) Trigger(err error) { + for _, callback := range this.callbacks { + callback(err) + } +} + +//endregion //////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/network/udp/server.go b/packages/network/udp/server.go new file mode 100644 index 0000000000000000000000000000000000000000..08f265ceac5d9d00f192033947458ece3a037670 --- /dev/null +++ b/packages/network/udp/server.go @@ -0,0 +1,64 @@ +package udp + +import ( + "net" +) + +type serverEvents struct { + Start *callbackEvent + Shutdown *callbackEvent + ReceiveData *dataConsumerEvent + Error *errorConsumerEvent +} + +type Server struct { + Socket *net.UDPConn + Events serverEvents +} + +func (this *Server) Shutdown() { + if this.Socket != nil { + socket := this.Socket + this.Socket = nil + + socket.Close() + } +} + +func (this *Server) Listen(address string, port int) { + if socket, err := net.ListenUDP("udp", &net.UDPAddr{ + Port: port, + IP: net.ParseIP(address), + }); err != nil { + this.Events.Error.Trigger(err) + + return + } else { + this.Socket = socket + } + + this.Events.Start.Trigger() + defer this.Events.Shutdown.Trigger() + + buf := make([]byte, 1500) + for this.Socket != nil { + if bytesRead, addr, err := this.Socket.ReadFromUDP(buf); err != nil { + if this.Socket != nil { + this.Events.Error.Trigger(err) + } + } else { + this.Events.ReceiveData.Trigger(addr, buf[:bytesRead]) + } + } +} + +func NewServer() *Server { + return &Server{ + Events: serverEvents{ + Start: &callbackEvent{make(map[uintptr]Callback)}, + Shutdown: &callbackEvent{make(map[uintptr]Callback)}, + ReceiveData: &dataConsumerEvent{make(map[uintptr]AddressDataConsumer)}, + Error: &errorConsumerEvent{make(map[uintptr]ErrorConsumer)}, + }, + } +} diff --git a/packages/network/udp/types.go b/packages/network/udp/types.go new file mode 100644 index 0000000000000000000000000000000000000000..5ee6236e770225de18527d7209f6aa2e8e67ae70 --- /dev/null +++ b/packages/network/udp/types.go @@ -0,0 +1,9 @@ +package udp + +import "net" + +type Callback = func() + +type AddressDataConsumer = func(addr *net.UDPAddr, data []byte) + +type ErrorConsumer = func(err error) diff --git a/packages/node/constants.go b/packages/node/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..4b4ea4fae61c166f5c9a5f21be64b7717b443b31 --- /dev/null +++ b/packages/node/constants.go @@ -0,0 +1,8 @@ +package node + +const ( + LOG_LEVEL_FAILURE = 0 + LOG_LEVEL_WARNING = 1 + LOG_LEVEL_SUCCESS = 2 + LOG_LEVEL_INFO = 3 +) diff --git a/packages/node/events.go b/packages/node/events.go new file mode 100644 index 0000000000000000000000000000000000000000..09c7327b31eb1d99bcf7de4b85ea2cece00b25e9 --- /dev/null +++ b/packages/node/events.go @@ -0,0 +1,28 @@ +package node + +import ( + "reflect" +) + +type pluginEvents struct { + Configure *callbackEvent + Run *callbackEvent +} + +type callbackEvent struct { + callbacks map[uintptr]Callback +} + +func (this *callbackEvent) Attach(callback Callback) { + this.callbacks[reflect.ValueOf(callback).Pointer()] = callback +} + +func (this *callbackEvent) Detach(callback Callback) { + delete(this.callbacks, reflect.ValueOf(callback).Pointer()) +} + +func (this *callbackEvent) Trigger(plugin *Plugin) { + for _, callback := range this.callbacks { + callback(plugin) + } +} diff --git a/packages/node/logger.go b/packages/node/logger.go new file mode 100644 index 0000000000000000000000000000000000000000..a2665c1eac539d1274f829302053e5eb0763dbf4 --- /dev/null +++ b/packages/node/logger.go @@ -0,0 +1,38 @@ +package node + +import "fmt" + +type Logger struct { + Enabled bool + LogInfo func(pluginName string, message string) + LogSuccess func(pluginName string, message string) + LogWarning func(pluginName string, message string) + LogFailure func(pluginName string, message string) +} + +func pluginPrefix(pluginName string) string { + var pluginPrefix string + if pluginName == "Node" { + pluginPrefix = "" + } else { + pluginPrefix = pluginName + ": " + } + + return pluginPrefix +} + +var DEFAULT_LOGGER = &Logger{ + Enabled: true, + LogSuccess: func(pluginName string, message string) { + fmt.Println("[ OK ] " + pluginPrefix(pluginName) + message) + }, + LogInfo: func(pluginName string, message string) { + fmt.Println("[ INFO ] " + pluginPrefix(pluginName) + message) + }, + LogWarning: func(pluginName string, message string) { + fmt.Println("[ WARN ] " + pluginPrefix(pluginName) + message) + }, + LogFailure: func(pluginName string, message string) { + fmt.Println("[ FAIL ] " + pluginPrefix(pluginName) + message) + }, +} diff --git a/packages/node/node.go b/packages/node/node.go new file mode 100644 index 0000000000000000000000000000000000000000..4f74e6524ae333dd4a418d74a700eca752855489 --- /dev/null +++ b/packages/node/node.go @@ -0,0 +1,122 @@ +package node + +import ( + "fmt" + "github.com/iotaledger/goshimmer/packages/daemon" + "sync" +) + +type Node struct { + wg *sync.WaitGroup + loggers []*Logger + loadedPlugins []*Plugin + logLevel int +} + +func Load(plugins ...*Plugin) *Node { + fmt.Println(" _____ _ _ ________ ______ ___ ___________ ") + fmt.Println(" / ___| | | |_ _| \\/ || \\/ || ___| ___ \\") + fmt.Println(" \\ `--.| |_| | | | | . . || . . || |__ | |_/ /") + fmt.Println(" `--. \\ _ | | | | |\\/| || |\\/| || __|| / ") + fmt.Println(" /\\__/ / | | |_| |_| | | || | | || |___| |\\ \\ ") + fmt.Println(" \\____/\\_| |_/\\___/\\_| |_/\\_| |_/\\____/\\_| \\_| fullnode 1.0") + fmt.Println() + + node := &Node{ + logLevel: LOG_LEVEL_INFO, + loggers: make([]*Logger, 0), + wg: &sync.WaitGroup{}, + loadedPlugins: make([]*Plugin, 0), + } + + node.AddLogger(DEFAULT_LOGGER) + node.Load(plugins...) + + return node +} + +func Run(plugins ...*Plugin) *Node { + node := Load(plugins...) + node.Run() + + return node +} + +func (node *Node) AddLogger(logger *Logger) { + node.loggers = append(node.loggers, logger) +} + +func (node *Node) LogSuccess(pluginName string, message string) { + if node.logLevel >= LOG_LEVEL_SUCCESS { + for _, logger := range node.loggers { + if logger.Enabled { + logger.LogSuccess(pluginName, message) + } + } + } +} + +func (node *Node) LogInfo(pluginName string, message string) { + if node.logLevel >= LOG_LEVEL_INFO { + for _, logger := range node.loggers { + if logger.Enabled { + logger.LogInfo(pluginName, message) + } + } + } +} + +func (node *Node) LogWarning(pluginName string, message string) { + if node.logLevel >= LOG_LEVEL_WARNING { + for _, logger := range node.loggers { + if logger.Enabled { + logger.LogWarning(pluginName, message) + } + } + } +} + +func (node *Node) LogFailure(pluginName string, message string) { + if node.logLevel >= LOG_LEVEL_FAILURE { + for _, logger := range node.loggers { + if logger.Enabled { + logger.LogFailure(pluginName, message) + } + } + } +} + +func (node *Node) Load(plugins ...*Plugin) { + node.LogInfo("Node", "Loading plugins ...") + + if len(plugins) >= 1 { + for _, plugin := range plugins { + plugin.wg = node.wg + plugin.Node = node + + plugin.Events.Configure.Trigger(plugin) + + node.LogInfo("Node", "Loading Plugin: "+plugin.Name+" ... done") + } + } + + node.loadedPlugins = append(node.loadedPlugins, plugins...) +} + +func (node *Node) Run() { + node.LogInfo("Node", "Executing plugins ...") + + if len(node.loadedPlugins) >= 1 { + for _, plugin := range node.loadedPlugins { + plugin.Events.Run.Trigger(plugin) + + node.LogSuccess("Node", "Starting Plugin: "+plugin.Name+" ... done") + } + } + + node.LogSuccess("Node", "Starting background workers ...") + + daemon.Run() + + node.LogSuccess("Node", "Shutdown complete!") +} diff --git a/packages/node/plugin.go b/packages/node/plugin.go new file mode 100644 index 0000000000000000000000000000000000000000..5ebf45df451238185072ce1b817b6d2337478d38 --- /dev/null +++ b/packages/node/plugin.go @@ -0,0 +1,51 @@ +package node + +import ( + "sync" +) + +type Plugin struct { + Node *Node + Name string + Events pluginEvents + wg *sync.WaitGroup +} + +func NewPlugin(name string, callback Callback, callbacks ...Callback) *Plugin { + plugin := &Plugin{ + Name: name, + Events: pluginEvents{ + Configure: &callbackEvent{make(map[uintptr]Callback)}, + Run: &callbackEvent{make(map[uintptr]Callback)}, + }, + } + + if len(callbacks) >= 1 { + plugin.Events.Configure.Attach(callback) + for _, callback = range callbacks[:len(callbacks)-1] { + plugin.Events.Configure.Attach(callback) + } + + plugin.Events.Run.Attach(callbacks[len(callbacks)-1]) + } else { + plugin.Events.Run.Attach(callback) + } + + return plugin +} + +func (plugin *Plugin) LogSuccess(message string) { + plugin.Node.LogSuccess(plugin.Name, message) +} + +func (plugin *Plugin) LogInfo(message string) { + plugin.Node.LogInfo(plugin.Name, message) +} + +func (plugin *Plugin) LogWarning(message string) { + plugin.Node.LogWarning(plugin.Name, message) +} + +func (plugin *Plugin) LogFailure(message string) { + plugin.Node.LogFailure(plugin.Name, message) +} diff --git a/packages/node/types.go b/packages/node/types.go new file mode 100644 index 0000000000000000000000000000000000000000..fff6b603572ce25e17f1aa9f7b96a0edc30df98d --- /dev/null +++ b/packages/node/types.go @@ -0,0 +1,3 @@ +package node + +type Callback = func(plugin *Plugin) diff --git a/packages/parameter/api.go b/packages/parameter/api.go new file mode 100644 index 0000000000000000000000000000000000000000..dc9df16dc445d4c7f1f3308a2b012c3fed5c68f1 --- /dev/null +++ b/packages/parameter/api.go @@ -0,0 +1,19 @@ +package parameter + +var ( + // expose int methods + AddInt = addInt + GetInt = getInt + GetInts = getInts + + // expose string methods + AddString = addString + GetString = getString + GetStrings = getStrings + + // expose events + Events = moduleEvents{ + AddInt: &intParameterEvent{make(map[uintptr]IntParameterConsumer)}, + AddString: &stringParameterEvent{make(map[uintptr]StringParameterConsumer)}, + } +) diff --git a/packages/parameter/events.go b/packages/parameter/events.go new file mode 100644 index 0000000000000000000000000000000000000000..7ca92df1de48b4fd4f39ebfab0a572a86e633a43 --- /dev/null +++ b/packages/parameter/events.go @@ -0,0 +1,52 @@ +package parameter + +import "reflect" + +type moduleEvents struct { + AddInt *intParameterEvent + AddString *stringParameterEvent +} + +//region intParameterEvent ///////////////////////////////////////////////////////////////////////////////////////////// + +type intParameterEvent struct { + callbacks map[uintptr]IntParameterConsumer +} + +func (this *intParameterEvent) Attach(callback IntParameterConsumer) { + this.callbacks[reflect.ValueOf(callback).Pointer()] = callback +} + +func (this *intParameterEvent) Detach(callback IntParameterConsumer) { + delete(this.callbacks, reflect.ValueOf(callback).Pointer()) +} + +func (this *intParameterEvent) Trigger(param *IntParameter) { + for _, callback := range this.callbacks { + callback(param) + } +} + +//endregion //////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//region stringParameterEvent ////////////////////////////////////////////////////////////////////////////////////////// + +type stringParameterEvent struct { + callbacks map[uintptr]StringParameterConsumer +} + +func (this *stringParameterEvent) Attach(callback StringParameterConsumer) { + this.callbacks[reflect.ValueOf(callback).Pointer()] = callback +} + +func (this *stringParameterEvent) Detach(callback StringParameterConsumer) { + delete(this.callbacks, reflect.ValueOf(callback).Pointer()) +} + +func (this *stringParameterEvent) Trigger(param *StringParameter) { + for _, callback := range this.callbacks { + callback(param) + } +} + +//endregion //////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/parameter/parameter.go b/packages/parameter/parameter.go new file mode 100644 index 0000000000000000000000000000000000000000..07553c5cffabd8e2f823e0caa48297fab1caf63a --- /dev/null +++ b/packages/parameter/parameter.go @@ -0,0 +1,59 @@ +package parameter + +var intParameters = make(map[string]*IntParameter) + +func addInt(name string, defaultValue int, description string) *IntParameter { + if intParameters[name] != nil { + panic("duplicate parameter - \"" + name + "\" was defined already") + } + + newParameter := &IntParameter{ + Name: name, + DefaultValue: defaultValue, + Value: &defaultValue, + Description: description, + } + + intParameters[name] = newParameter + + Events.AddInt.Trigger(newParameter) + + return newParameter +} + +func getInt(name string) *IntParameter { + return intParameters[name] +} + +func getInts() map[string]*IntParameter { + return intParameters +} + +var stringParameters = make(map[string]*StringParameter) + +func addString(name string, defaultValue string, description string) *StringParameter { + if intParameters[name] != nil { + panic("duplicate parameter - \"" + name + "\" was defined already") + } + + newParameter := &StringParameter{ + Name: name, + DefaultValue: defaultValue, + Value: &defaultValue, + Description: description, + } + + stringParameters[name] = newParameter + + Events.AddString.Trigger(newParameter) + + return stringParameters[name] +} + +func getString(name string) *StringParameter { + return stringParameters[name] +} + +func getStrings() map[string]*StringParameter { + return stringParameters +} diff --git a/packages/parameter/types.go b/packages/parameter/types.go new file mode 100644 index 0000000000000000000000000000000000000000..3c5f3c62d835ea3060a483a1275d89a89f993cd9 --- /dev/null +++ b/packages/parameter/types.go @@ -0,0 +1,19 @@ +package parameter + +type IntParameter struct { + Name string + Value *int + DefaultValue int + Description string +} + +type StringParameter struct { + Name string + Value *string + DefaultValue string + Description string +} + +type IntParameterConsumer = func(param *IntParameter) + +type StringParameterConsumer = func(param *StringParameter) diff --git a/packages/settings/settings.go b/packages/settings/settings.go new file mode 100644 index 0000000000000000000000000000000000000000..d2a0862f46059ad7fb628fcc176a9ecac94de080 --- /dev/null +++ b/packages/settings/settings.go @@ -0,0 +1,21 @@ +package settings + +import "github.com/iotaledger/goshimmer/packages/database" + +var settingsDatabase database.Database + +func init() { + if db, err := database.Get("settings"); err != nil { + panic(err) + } else { + settingsDatabase = db + } +} + +func Get(key []byte) ([]byte, error) { + return settingsDatabase.Get(key) +} + +func Set(key []byte, value []byte) error { + return settingsDatabase.Set(key, value) +} \ No newline at end of file diff --git a/packages/transaction/constants.go b/packages/transaction/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..2320114fab0ecd7dc8cbeca2d1c8c9575df6bfd1 --- /dev/null +++ b/packages/transaction/constants.go @@ -0,0 +1,48 @@ +package transaction + +const ( + // sizes of the transaction fields + SIGNATURE_MESSAGE_FRAGMENT_SIZE = 6561 + ADDRESS_SIZE = 243 + VALUE_SIZE = 81 + TIMESTAMP_SIZE = 27 + CURRENT_INDEX_SIZE = 27 + LATEST_INDEX_SIZE = 27 + BUNDLE_HASH_SIZE = 243 + TRUNK_TRANSACTION_HASH_SIZE = 243 + BRANCH_TRANSACTION_HASH_SIZE = 243 + TAG_SIZE = 81 + NODE_ID_SIZE = 243 + NONCE_SIZE = 81 + + // offsets of the transaction fields + SIGNATURE_MESSAGE_FRAGMENT_OFFSET = 0 + ADDRESS_OFFSET = SIGNATURE_MESSAGE_FRAGMENT_END + VALUE_OFFSET = ADDRESS_END + TIMESTAMP_OFFSET = VALUE_END + CURRENT_INDEX_OFFSET = TIMESTAMP_END + LATEST_INDEX_OFFSET = CURRENT_INDEX_END + BUNDLE_HASH_OFFSET = LATEST_INDEX_END + TRUNK_TRANSACTION_HASH_OFFSET = BUNDLE_HASH_END + BRANCH_TRANSACTION_HASH_OFFSET = TRUNK_TRANSACTION_HASH_END + TAG_OFFSET = BRANCH_TRANSACTION_HASH_END + NODE_ID_OFFSET = TAG_END + NONCE_OFFSET = NODE_ID_END + + // ends of the transaction fields + SIGNATURE_MESSAGE_FRAGMENT_END = SIGNATURE_MESSAGE_FRAGMENT_OFFSET + SIGNATURE_MESSAGE_FRAGMENT_SIZE + ADDRESS_END = ADDRESS_OFFSET + ADDRESS_SIZE + VALUE_END = VALUE_OFFSET + VALUE_SIZE + TIMESTAMP_END = TIMESTAMP_OFFSET + TIMESTAMP_SIZE + CURRENT_INDEX_END = CURRENT_INDEX_OFFSET + CURRENT_INDEX_SIZE + LATEST_INDEX_END = LATEST_INDEX_OFFSET + LATEST_INDEX_SIZE + BUNDLE_HASH_END = BUNDLE_HASH_OFFSET + BUNDLE_HASH_SIZE + TRUNK_TRANSACTION_HASH_END = TRUNK_TRANSACTION_HASH_OFFSET + TRUNK_TRANSACTION_HASH_SIZE + BRANCH_TRANSACTION_HASH_END = BRANCH_TRANSACTION_HASH_OFFSET + BRANCH_TRANSACTION_HASH_SIZE + TAG_END = TAG_OFFSET + TAG_SIZE + NODE_ID_END = NODE_ID_OFFSET + NODE_ID_SIZE + NONCE_END = NONCE_OFFSET + NONCE_SIZE + + // the full size of a transaction + TRANSACTION_SIZE = NONCE_END +) diff --git a/packages/transaction/transaction.go b/packages/transaction/transaction.go new file mode 100644 index 0000000000000000000000000000000000000000..84e486723fca89f2fec60eff304629265771a825 --- /dev/null +++ b/packages/transaction/transaction.go @@ -0,0 +1,58 @@ +package transaction + +import ( + "github.com/iotadevelopment/go/packages/curl" + "github.com/iotadevelopment/go/packages/ternary" +) + +type Transaction struct { + SignatureMessageFragment ternary.Trits + Address ternary.Trits + Value int64 + Timestamp uint64 + CurrentIndex uint64 + LatestIndex uint64 + BundleHash ternary.Trits + TrunkTransactionHash ternary.Trits + BranchTransactionHash ternary.Trits + Tag ternary.Trits + NodeId ternary.Trits + Nonce ternary.Trits + + Hash ternary.Trits + WeightMagnitude int + Bytes []byte + Trits ternary.Trits +} + +func FromTrits(trits ternary.Trits, optionalHash ...ternary.Trits) *Transaction { + hash := <- curl.CURLP81.Hash(trits) + + transaction := &Transaction{ + SignatureMessageFragment: trits[SIGNATURE_MESSAGE_FRAGMENT_OFFSET:SIGNATURE_MESSAGE_FRAGMENT_END], + Address: trits[ADDRESS_OFFSET:ADDRESS_END], + Value: trits[VALUE_OFFSET:VALUE_END].ToInt64(), + Timestamp: trits[TIMESTAMP_OFFSET:TIMESTAMP_END].ToUint64(), + CurrentIndex: trits[CURRENT_INDEX_OFFSET:CURRENT_INDEX_END].ToUint64(), + LatestIndex: trits[LATEST_INDEX_OFFSET:LATEST_INDEX_END].ToUint64(), + BundleHash: trits[BUNDLE_HASH_OFFSET:BUNDLE_HASH_END], + TrunkTransactionHash: trits[TRUNK_TRANSACTION_HASH_OFFSET:TRUNK_TRANSACTION_HASH_END], + BranchTransactionHash: trits[BRANCH_TRANSACTION_HASH_OFFSET:BRANCH_TRANSACTION_HASH_END], + Tag: trits[TAG_OFFSET:TAG_END], + NodeId: trits[NODE_ID_OFFSET:NODE_ID_END], + Nonce: trits[NONCE_OFFSET:NONCE_END], + + Hash: hash, + WeightMagnitude: hash.TrailingZeroes(), + Trits: trits, + } + + return transaction +} + +func FromBytes(bytes []byte) *Transaction { + transaction := FromTrits(ternary.BytesToTrits(bytes)[:TRANSACTION_SIZE]) + transaction.Bytes = bytes + + return transaction +} \ No newline at end of file diff --git a/plugins/autopeering/autopeering.go b/plugins/autopeering/autopeering.go new file mode 100644 index 0000000000000000000000000000000000000000..d26a4e1f4b2292f5e583aa05197004bc86200594 --- /dev/null +++ b/plugins/autopeering/autopeering.go @@ -0,0 +1,25 @@ +package autopeering + +import ( + "github.com/iotaledger/goshimmer/packages/daemon" + "github.com/iotaledger/goshimmer/packages/node" + "github.com/iotaledger/goshimmer/plugins/autopeering/peermanager" + "github.com/iotaledger/goshimmer/plugins/autopeering/server" +) + +func configure(plugin *node.Plugin) { + server.Configure(plugin) + peermanager.Configure(plugin) + + daemon.Events.Shutdown.Attach(func() { + server.Shutdown(plugin) + peermanager.Shutdown(plugin) + }) +} + +func run(plugin *node.Plugin) { + server.Run(plugin) + peermanager.Run(plugin) +} + +var PLUGIN = node.NewPlugin("Auto Peering", configure, run) diff --git a/plugins/autopeering/parameters/parameters.go b/plugins/autopeering/parameters/parameters.go new file mode 100644 index 0000000000000000000000000000000000000000..63014eda145c558ab2740ea7e46bbcac863beb05 --- /dev/null +++ b/plugins/autopeering/parameters/parameters.go @@ -0,0 +1,9 @@ +package parameters + +import "github.com/iotaledger/goshimmer/packages/parameter" + +var ( + ADDRESS = parameter.AddString("AUTOPEERING/ADDRESS", "0.0.0.0", "address to bind for incoming peering requests") + UDP_PORT = parameter.AddInt("AUTOPEERING/UDP_PORT", 14626, "udp port for incoming peering requests") + ENTRY_NODES = parameter.AddString("AUTOPEERING/ENTRY_NODES", "tcp://82.165.29.179:14626", "list of trusted entry nodes for auto peering") +) diff --git a/plugins/autopeering/peermanager/constants.go b/plugins/autopeering/peermanager/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..80fa7543ab834406b25ad6b1356253ec6b7e113b --- /dev/null +++ b/plugins/autopeering/peermanager/constants.go @@ -0,0 +1,12 @@ +package peermanager + +import ( + "github.com/iotaledger/goshimmer/packages/identity" + "time" +) + +const ( + FIND_NEIGHBOR_INTERVAL = 5 * time.Second +) + +var UNKNOWN_IDENTITY = identity.GenerateRandomIdentity() diff --git a/plugins/autopeering/peermanager/entry_nodes.go b/plugins/autopeering/peermanager/entry_nodes.go new file mode 100644 index 0000000000000000000000000000000000000000..799192128b0019887dbafdfb9255258ff15edb13 --- /dev/null +++ b/plugins/autopeering/peermanager/entry_nodes.go @@ -0,0 +1,74 @@ +package peermanager + +import ( + "github.com/iotaledger/goshimmer/plugins/autopeering/parameters" + "github.com/iotaledger/goshimmer/plugins/autopeering/protocol" + "net" + "strconv" + "strings" +) + +func getEntryNodes() []*protocol.Peer { + result := make([]*protocol.Peer, 0) + + for _, entryNodeDefinition := range strings.Fields(*parameters.ENTRY_NODES.Value) { + if entryNodeDefinition == "" { + continue + } + + entryNode := &protocol.Peer{ + Identity: UNKNOWN_IDENTITY, + } + + protocolBits := strings.Split(entryNodeDefinition, "://") + if len(protocolBits) != 2 { + panic("invalid entry in list of trusted entry nodes: " + entryNodeDefinition) + } + switch protocolBits[0] { + case "tcp": + entryNode.PeeringProtocolType = protocol.TCP_PROTOCOL + case "udp": + entryNode.PeeringProtocolType = protocol.UDP_PROTOCOL + } + + addressBits := strings.Split(protocolBits[1], ":") + switch len(addressBits) { + case 2: + host := addressBits[0] + port, err := strconv.Atoi(addressBits[1]) + if err != nil { + panic("error while parsing port of entry in list of entry nodes") + } + + ip := net.ParseIP(host) + if ip == nil { + panic("error while parsing ip of entry in list of entry nodes") + } + + entryNode.Address = ip + entryNode.PeeringPort = uint16(port) + case 6: + host := strings.Join(addressBits[:5], ":") + port, err := strconv.Atoi(addressBits[5]) + if err != nil { + panic("error while parsing port of entry in list of entry nodes") + } + + ip := net.ParseIP(host) + if ip == nil { + panic("error while parsing ip of entry in list of entry nodes") + } + + entryNode.Address = ip + entryNode.PeeringPort = uint16(port) + default: + panic("invalid entry in list of trusted entry nodes: " + entryNodeDefinition) + } + + result = append(result, entryNode) + } + + return result +} + +var ENTRY_NODES = getEntryNodes() \ No newline at end of file diff --git a/plugins/autopeering/peermanager/peer_list.go b/plugins/autopeering/peermanager/peer_list.go new file mode 100644 index 0000000000000000000000000000000000000000..f7a4a73313e6f7f596ef815a35b4de7b45f3f48f --- /dev/null +++ b/plugins/autopeering/peermanager/peer_list.go @@ -0,0 +1,50 @@ +package peermanager + +import ( + "bytes" + "github.com/iotaledger/goshimmer/packages/accountability" + "github.com/iotaledger/goshimmer/plugins/autopeering/protocol" + "time" +) + +type PeerList struct { + Peers map[string]*protocol.Peer +} + +func (this *PeerList) Update(peer *protocol.Peer) { + if peer.Identity == UNKNOWN_IDENTITY || bytes.Equal(peer.Identity.Identifier, accountability.OWN_ID.Identifier) { + return + } + + now := time.Now() + + if existingPeer, exists := this.Peers[peer.Identity.StringIdentifier]; exists { + existingPeer.Address = peer.Address + existingPeer.GossipPort = peer.GossipPort + existingPeer.PeeringPort = peer.PeeringPort + existingPeer.LastSeen = now + existingPeer.LastContact = now + + // trigger update peer + } else { + peer.FirstSeen = now + peer.LastSeen = now + peer.LastContact = now + + this.Peers[peer.Identity.StringIdentifier] = peer + + // trigger add peer + } +} + +func (this *PeerList) Add(peer *protocol.Peer) { + this.Peers[peer.Identity.StringIdentifier] = peer +} + +func (this *PeerList) Contains(key string) bool { + if _, exists := this.Peers[key]; exists { + return true + } else { + return false + } +} diff --git a/plugins/autopeering/peermanager/peer_manager.go b/plugins/autopeering/peermanager/peer_manager.go new file mode 100644 index 0000000000000000000000000000000000000000..62253cf5cd25d298f9a69102c88859a1925186e7 --- /dev/null +++ b/plugins/autopeering/peermanager/peer_manager.go @@ -0,0 +1,201 @@ +package peermanager + +import ( + "github.com/iotaledger/goshimmer/packages/accountability" + "github.com/iotaledger/goshimmer/packages/daemon" + "github.com/iotaledger/goshimmer/packages/network" + "github.com/iotaledger/goshimmer/packages/node" + "github.com/iotaledger/goshimmer/plugins/autopeering/parameters" + "github.com/iotaledger/goshimmer/plugins/autopeering/protocol" + "github.com/iotaledger/goshimmer/plugins/autopeering/salt" + "github.com/iotaledger/goshimmer/plugins/autopeering/saltmanager" + "github.com/iotaledger/goshimmer/plugins/autopeering/server" + "math" + "net" + "strconv" + "time" +) + +var PEERING_REQUEST *protocol.PeeringRequest + +var KNOWN_PEERS = &PeerList{make(map[string]*protocol.Peer)} + +var CHOSEN_NEIGHBORS = &PeerList{make(map[string]*protocol.Peer)} + +var ACCEPTED_NEIGHBORS = &PeerList{make(map[string]*protocol.Peer)} + +func configurePeeringRequest() { + PEERING_REQUEST = &protocol.PeeringRequest{ + Issuer: &protocol.Peer{ + Identity: accountability.OWN_ID, + PeeringProtocolType: protocol.TCP_PROTOCOL, + PeeringPort: uint16(*parameters.UDP_PORT.Value), + GossipProtocolType: protocol.TCP_PROTOCOL, + GossipPort: uint16(*parameters.UDP_PORT.Value), + Address: net.IPv4(0, 0, 0, 0), + }, + Salt: saltmanager.PUBLIC_SALT, + } + PEERING_REQUEST.Sign() + + saltmanager.Events.UpdatePublicSalt.Attach(func(salt *salt.Salt) { + PEERING_REQUEST.Sign() + }) +} + +func Configure(plugin *node.Plugin) { + configurePeeringRequest() + + server.Events.ReceivePeeringRequest.Attach(func(ip net.IP, peeringRequest *protocol.PeeringRequest) { + peer := peeringRequest.Issuer + peer.Address = ip + + KNOWN_PEERS.Update(peer) + + plugin.LogInfo("received peering request from " + peeringRequest.Issuer.Identity.StringIdentifier) + }) + server.Events.ReceiveTCPPeeringRequest.Attach(func(conn network.Connection, request *protocol.PeeringRequest) { + peer := request.Issuer + peer.Address = conn.GetConnection().RemoteAddr().(*net.TCPAddr).IP + + KNOWN_PEERS.Update(peer) + + plugin.LogInfo("received peering request from " + request.Issuer.Identity.StringIdentifier) + + sendPeeringResponse(conn.GetConnection()) + }) + server.Events.Error.Attach(func(ip net.IP, err error) { + plugin.LogFailure("invalid peering request from " + ip.String()) + }) +} + +func Run(plugin *node.Plugin) { + daemon.BackgroundWorker(func() { + chooseNeighbors(plugin) + + ticker := time.NewTicker(FIND_NEIGHBOR_INTERVAL) + for { + select { + case <- daemon.ShutdownSignal: + return + case <- ticker.C: + chooseNeighbors(plugin) + } + } + }) +} + +func Shutdown(plugin *node.Plugin) {} + +func generateProposedNodeCandidates() []*protocol.Peer { + peers := make([]*protocol.Peer, 0) + for _, peer := range KNOWN_PEERS.Peers { + peers = append(peers, peer) + } + + return peers +} + +func rejectPeeringRequest(conn net.Conn) { + conn.Write((&protocol.PeeringResponse{ + Type: protocol.PEERING_RESPONSE_REJECT, + Issuer: PEERING_REQUEST.Issuer, + Peers: generateProposedNodeCandidates(), + }).Sign().Marshal()) + conn.Close() +} + +func acceptPeeringRequest(conn net.Conn) { + conn.Write((&protocol.PeeringResponse{ + Type: protocol.PEERING_RESPONSE_ACCEPT, + Issuer: PEERING_REQUEST.Issuer, + Peers: generateProposedNodeCandidates(), + }).Sign().Marshal()) + conn.Close() +} + +func sendPeeringResponse(conn net.Conn) { + if len(ACCEPTED_NEIGHBORS.Peers) < protocol.NEIGHBOR_COUNT / 2 { + acceptPeeringRequest(conn) + } else { + rejectPeeringRequest(conn) + } +} + +func sendPeeringRequest(plugin *node.Plugin, peer *protocol.Peer) { + var protocolString string + switch peer.PeeringProtocolType { + case protocol.TCP_PROTOCOL: + protocolString = "tcp" + case protocol.UDP_PROTOCOL: + protocolString = "udp" + default: + panic("invalid protocol in known peers") + } + + conn, err := net.Dial(protocolString, peer.Address.String() + ":" + strconv.Itoa(int(peer.PeeringPort))) + if err != nil { + plugin.LogFailure(err.Error()) + } else { + conn := network.NewPeer(protocolString, conn) + + conn.Write(PEERING_REQUEST.Marshal()) + + buffer := make([]byte, protocol.PEERING_RESPONSE_MARSHALLED_TOTAL_SIZE) + offset := 0 + conn.OnReceiveData(func(data []byte) { + remainingCapacity := int(math.Min(float64(protocol.PEERING_RESPONSE_MARSHALLED_TOTAL_SIZE - offset), float64(len(data)))) + + copy(buffer[offset:], data[:remainingCapacity]) + offset += len(data) + + if offset >= protocol.PEERING_RESPONSE_MARSHALLED_TOTAL_SIZE { + peeringResponse, err := protocol.UnmarshalPeeringResponse(buffer) + if err != nil { + plugin.LogFailure("invalid peering response from " + conn.GetConnection().RemoteAddr().String()) + } else { + processPeeringResponse(plugin, peeringResponse) + } + + conn.GetConnection().Close() + } + }) + + go conn.HandleConnection() + } +} + +func processPeeringResponse(plugin *node.Plugin, response *protocol.PeeringResponse) { + KNOWN_PEERS.Update(response.Issuer) + for _, peer := range response.Peers { + KNOWN_PEERS.Update(peer) + } + + switch response.Type { + case protocol.PEERING_RESPONSE_ACCEPT: + CHOSEN_NEIGHBORS.Update(response.Issuer) + case protocol.PEERING_RESPONSE_REJECT: + default: + plugin.LogInfo("invalid response type in peering response of " + response.Issuer.Address.String() + ":" + strconv.Itoa(int(response.Issuer.PeeringPort))) + } +} + +func getChosenNeighborCandidates() []*protocol.Peer { + result := make([]*protocol.Peer, 0) + + for _, peer := range KNOWN_PEERS.Peers { + result = append(result, peer) + } + + for _, peer := range ENTRY_NODES { + result = append(result, peer) + } + + return result +} + +func chooseNeighbors(plugin *node.Plugin) { + for _, peer := range getChosenNeighborCandidates() { + sendPeeringRequest(plugin, peer) + } +} diff --git a/plugins/autopeering/protocol/constants.go b/plugins/autopeering/protocol/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..5e677f1691436bba0e453fb5fa9e0b3ffa212fe6 --- /dev/null +++ b/plugins/autopeering/protocol/constants.go @@ -0,0 +1,85 @@ +package protocol + +import ( + "github.com/iotaledger/goshimmer/packages/identity" + "github.com/iotaledger/goshimmer/plugins/autopeering/salt" +) + +const ( + // PEERING REQUEST PACKET STRUCTURE //////////////////////////////////////////////////////////////////////////////// + + NEIGHBOR_COUNT = 8 + + PACKET_HEADER_SIZE = 1 + ISSUER_SIZE = PEER_MARSHALLED_TOTAL_SIZE + SALT_SIZE = salt.SALT_MARSHALLED_SIZE + SIGNATURE_SIZE = 65 + + PACKET_HEADER_START = 0 + ISSUER_START = PACKET_HEADER_END + SALT_START = ISSUER_END + SIGNATURE_START = SALT_END + + PACKET_HEADER_END = PACKET_HEADER_START + PACKET_HEADER_SIZE + ISSUER_END = ISSUER_START + ISSUER_SIZE + SALT_END = SALT_START + SALT_SIZE + SIGNATURE_END = SIGNATURE_START + SIGNATURE_SIZE + + PEERING_REQUEST_MARSHALLED_TOTAL_SIZE = SIGNATURE_END + + PACKET_HEADER = 0xBE + + PEERING_RESPONSE_REJECT = ResponseType(0) + PEERING_RESPONSE_ACCEPT = ResponseType(1) + + PEERING_RESPONSE_MARSHALLED_PEERS_AMOUNT = NEIGHBOR_COUNT + NEIGHBOR_COUNT*NEIGHBOR_COUNT + + PEERING_RESPONSE_MARSHALLED_TYPE_SIZE = 1 + PEERING_RESPONSE_MARSHALLED_ISSUER_SIZE = PEER_MARSHALLED_TOTAL_SIZE + PEERING_RESPONSE_MARSHALLED_PEER_FLAG_SIZE = 1 + PEERING_RESPONSE_MARSHALLED_PEER_SIZE = PEERING_RESPONSE_MARSHALLED_PEER_FLAG_SIZE + PEER_MARSHALLED_TOTAL_SIZE + PEERING_RESPONSE_MARSHALLED_PEERS_SIZE = PEERING_RESPONSE_MARSHALLED_PEERS_AMOUNT * PEERING_RESPONSE_MARSHALLED_PEER_SIZE + PEERING_RESPONSE_MARSHALLED_SIGNATURE_SIZE = 65 + PEERING_RESPONSE_MARSHALLED_TOTAL_SIZE = PEERING_RESPONSE_MARSHALLED_SIGNATURE_END + + PEERING_RESPONSE_MARSHALLED_TYPE_START = 0 + PEERING_RESPONSE_MARSHALLED_ISSUER_START = PEERING_RESPONSE_MARSHALLED_TYPE_END + PEERING_RESPONSE_MARSHALLED_PEERS_START = PEERING_RESPONSE_MARSHALLED_ISSUER_END + PEERING_RESPONSE_MARSHALLED_SIGNATURE_START = PEERING_RESPONSE_MARSHALLED_PEERS_END + + PEERING_RESPONSE_MARSHALLED_TYPE_END = PEERING_RESPONSE_MARSHALLED_TYPE_START + PEERING_RESPONSE_MARSHALLED_TYPE_SIZE + PEERING_RESPONSE_MARSHALLED_PEERS_END = PEERING_RESPONSE_MARSHALLED_PEERS_START + PEERING_RESPONSE_MARSHALLED_PEERS_SIZE + PEERING_RESPONSE_MARSHALLED_ISSUER_END = PEERING_RESPONSE_MARSHALLED_ISSUER_START + PEERING_RESPONSE_MARSHALLED_ISSUER_SIZE + PEERING_RESPONSE_MARSHALLED_SIGNATURE_END = PEERING_RESPONSE_MARSHALLED_SIGNATURE_START + PEERING_RESPONSE_MARSHALLED_SIGNATURE_SIZE + + PEER_MARSHALLED_PUBLIC_KEY_SIZE = identity.PUBLIC_KEY_BYTE_LENGTH + PEER_MARSHALLED_ADDRESS_TYPE_SIZE = 1 // ipv4/ipv6 + PEER_MARSHALLED_ADDRESS_SIZE = 16 + PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_SIZE = 1 // tcp udp + PEER_MARSHALLED_PEERING_PORT_SIZE = 2 + PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_SIZE = 1 + PEER_MARSHALLED_GOSSIP_PORT_SIZE = 2 // tcp udp + PEER_MARSHALLED_TOTAL_SIZE = PEER_MARSHALLED_GOSSIP_PORT_END + + PEER_MARSHALLED_PUBLIC_KEY_START = 0 + PEER_MARSHALLED_ADDRESS_TYPE_START = PEER_MARSHALLED_PUBLIC_KEY_END + PEER_MARSHALLED_ADDRESS_START = PEER_MARSHALLED_ADDRESS_TYPE_END + PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_START = PEER_MARSHALLED_ADDRESS_END + PEER_MARSHALLED_PEERING_PORT_START = PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_END + PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_START = PEER_MARSHALLED_PEERING_PORT_END + PEER_MARSHALLED_GOSSIP_PORT_START = PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_END + + PEER_MARSHALLED_PUBLIC_KEY_END = PEER_MARSHALLED_PUBLIC_KEY_START + PEER_MARSHALLED_PUBLIC_KEY_SIZE + PEER_MARSHALLED_ADDRESS_TYPE_END = PEER_MARSHALLED_ADDRESS_TYPE_START + PEER_MARSHALLED_ADDRESS_TYPE_SIZE + PEER_MARSHALLED_ADDRESS_END = PEER_MARSHALLED_ADDRESS_START + PEER_MARSHALLED_ADDRESS_SIZE + PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_END = PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_START + PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_SIZE + PEER_MARSHALLED_PEERING_PORT_END = PEER_MARSHALLED_PEERING_PORT_START + PEER_MARSHALLED_PEERING_PORT_SIZE + PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_END = PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_START + PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_SIZE + PEER_MARSHALLED_GOSSIP_PORT_END = PEER_MARSHALLED_GOSSIP_PORT_START + PEER_MARSHALLED_GOSSIP_PORT_SIZE + + PEER_MARSHALLED_ADDRESS_TYPE_IPV4 = AddressType(0) + PEER_MARSHALLED_ADDRESS_TYPE_IPV6 = AddressType(1) + + TCP_PROTOCOL = ProtocolType(0) + UDP_PROTOCOL = ProtocolType(1) +) diff --git a/plugins/autopeering/protocol/errors.go b/plugins/autopeering/protocol/errors.go new file mode 100644 index 0000000000000000000000000000000000000000..53f4ab081ff3274a3057eefc7f63cc1fdaad3567 --- /dev/null +++ b/plugins/autopeering/protocol/errors.go @@ -0,0 +1,10 @@ +package protocol + +import "github.com/pkg/errors" + +var ( + ErrPublicSaltExpired = errors.New("expired public salt in peering request") + ErrPublicSaltInvalidLifetime = errors.New("invalid public salt lifetime") + ErrInvalidSignature = errors.New("invalid signature in peering request") + ErrMalformedPeeringRequest = errors.New("malformed peering request") +) diff --git a/plugins/autopeering/protocol/peer.go b/plugins/autopeering/protocol/peer.go new file mode 100644 index 0000000000000000000000000000000000000000..013de5495610e5884de4835ce4c6c336d09b67fb --- /dev/null +++ b/plugins/autopeering/protocol/peer.go @@ -0,0 +1,72 @@ +package protocol + +import ( + "encoding/binary" + "github.com/iotaledger/goshimmer/packages/identity" + "github.com/pkg/errors" + "net" + "time" +) + +type Peer struct { + Identity *identity.Identity + Address net.IP + PeeringProtocolType ProtocolType + PeeringPort uint16 + GossipProtocolType ProtocolType + GossipPort uint16 + FirstSeen time.Time + LastSeen time.Time + LastContact time.Time +} + +func UnmarshalPeer(data []byte) (*Peer, error) { + if len(data) < PEER_MARSHALLED_TOTAL_SIZE { + return nil, errors.New("size of marshalled peer is too small") + } + + peer := &Peer{ + Identity: identity.NewIdentity(data[PEER_MARSHALLED_PUBLIC_KEY_START:PEER_MARSHALLED_PUBLIC_KEY_END]), + } + + switch data[PEER_MARSHALLED_ADDRESS_TYPE_START] { + case PEER_MARSHALLED_ADDRESS_TYPE_IPV4: + peer.Address = net.IP(data[PEER_MARSHALLED_ADDRESS_START:PEER_MARSHALLED_ADDRESS_END]).To4() + case PEER_MARSHALLED_ADDRESS_TYPE_IPV6: + peer.Address = net.IP(data[PEER_MARSHALLED_ADDRESS_START:PEER_MARSHALLED_ADDRESS_END]).To16() + } + + peer.PeeringProtocolType = ProtocolType(data[PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_START]) + peer.PeeringPort = binary.BigEndian.Uint16(data[PEER_MARSHALLED_PEERING_PORT_START:PEER_MARSHALLED_PEERING_PORT_END]) + + peer.GossipProtocolType = ProtocolType(data[PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_START]) + peer.GossipPort = binary.BigEndian.Uint16(data[PEER_MARSHALLED_GOSSIP_PORT_START:PEER_MARSHALLED_GOSSIP_PORT_END]) + + return peer, nil +} + +func (peer *Peer) Marshal() []byte { + result := make([]byte, PEER_MARSHALLED_TOTAL_SIZE) + + copy(result[PEER_MARSHALLED_PUBLIC_KEY_START:PEER_MARSHALLED_PUBLIC_KEY_END], + peer.Identity.PublicKey[:PEER_MARSHALLED_PUBLIC_KEY_SIZE]) + + switch len(peer.Address) { + case net.IPv4len: + result[PEER_MARSHALLED_ADDRESS_TYPE_START] = PEER_MARSHALLED_ADDRESS_TYPE_IPV4 + case net.IPv6len: + result[PEER_MARSHALLED_ADDRESS_TYPE_START] = PEER_MARSHALLED_ADDRESS_TYPE_IPV6 + default: + panic("invalid address in peer") + } + + copy(result[PEER_MARSHALLED_ADDRESS_START:PEER_MARSHALLED_ADDRESS_END], peer.Address.To16()) + + result[PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_START] = peer.PeeringProtocolType + binary.BigEndian.PutUint16(result[PEER_MARSHALLED_PEERING_PORT_START:PEER_MARSHALLED_PEERING_PORT_END], peer.PeeringPort) + + result[PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_START] = peer.GossipProtocolType + binary.BigEndian.PutUint16(result[PEER_MARSHALLED_GOSSIP_PORT_START:PEER_MARSHALLED_GOSSIP_PORT_END], peer.GossipPort) + + return result +} diff --git a/plugins/autopeering/protocol/peering_request.go b/plugins/autopeering/protocol/peering_request.go new file mode 100644 index 0000000000000000000000000000000000000000..1ed834db1c9097a8f757357e3c6f429805550c03 --- /dev/null +++ b/plugins/autopeering/protocol/peering_request.go @@ -0,0 +1,74 @@ +package protocol + +import ( + "bytes" + "github.com/iotaledger/goshimmer/packages/identity" + "github.com/iotaledger/goshimmer/plugins/autopeering/salt" + "github.com/iotaledger/goshimmer/plugins/autopeering/saltmanager" + "time" +) + +type PeeringRequest struct { + Issuer *Peer + Salt *salt.Salt + Signature [SIGNATURE_SIZE]byte +} + +func UnmarshalPeeringRequest(data []byte) (*PeeringRequest, error) { + if data[0] != PACKET_HEADER || len(data) != PEERING_REQUEST_MARSHALLED_TOTAL_SIZE { + return nil, ErrMalformedPeeringRequest + } + + peeringRequest := &PeeringRequest{} + + if unmarshalledPeer, err := UnmarshalPeer(data[ISSUER_START:ISSUER_END]); err != nil { + return nil, err + } else { + peeringRequest.Issuer = unmarshalledPeer + } + + if unmarshalledSalt, err := salt.Unmarshal(data[SALT_START:SALT_END]); err != nil { + return nil, err + } else { + peeringRequest.Salt = unmarshalledSalt + } + + now := time.Now() + if peeringRequest.Salt.ExpirationTime.Before(now) { + return nil, ErrPublicSaltExpired + } + if peeringRequest.Salt.ExpirationTime.After(now.Add(saltmanager.PUBLIC_SALT_LIFETIME + 1*time.Minute)) { + return nil, ErrPublicSaltInvalidLifetime + } + + if issuer, err := identity.FromSignedData(data[:SIGNATURE_START], data[SIGNATURE_START:]); err != nil { + return nil, err + } else { + if !bytes.Equal(issuer.Identifier, peeringRequest.Issuer.Identity.Identifier) { + return nil, ErrInvalidSignature + } + } + copy(peeringRequest.Signature[:], data[SIGNATURE_START:SIGNATURE_END]) + + return peeringRequest, nil +} + +func (this *PeeringRequest) Sign() { + dataToSign := this.Marshal()[:SIGNATURE_START] + if signature, err := this.Issuer.Identity.Sign(dataToSign); err != nil { + panic(err) + } else { + copy(this.Signature[:], signature) + } +} + +func (this *PeeringRequest) Marshal() []byte { + result := make([]byte, PEERING_REQUEST_MARSHALLED_TOTAL_SIZE) + + result[PACKET_HEADER_START] = PACKET_HEADER + copy(result[ISSUER_START:ISSUER_END], this.Issuer.Marshal()) + copy(result[SALT_START:SALT_END], this.Salt.Marshal()) + copy(result[SIGNATURE_START:SIGNATURE_END], this.Signature[:SIGNATURE_SIZE]) + + return result +} diff --git a/plugins/autopeering/protocol/peering_response.go b/plugins/autopeering/protocol/peering_response.go new file mode 100644 index 0000000000000000000000000000000000000000..1cb9dee23d7584b79f4861193f33d4028776380c --- /dev/null +++ b/plugins/autopeering/protocol/peering_response.go @@ -0,0 +1,87 @@ +package protocol + +import ( + "bytes" + "github.com/iotaledger/goshimmer/packages/identity" + "github.com/pkg/errors" +) + +type PeeringResponse struct { + Type ResponseType + Issuer *Peer + Peers []*Peer + Signature [PEERING_RESPONSE_MARSHALLED_SIGNATURE_SIZE]byte +} + +func UnmarshalPeeringResponse(data []byte) (*PeeringResponse, error) { + if len(data) < PEERING_RESPONSE_MARSHALLED_TOTAL_SIZE { + return nil, errors.New("size of marshalled peering response is too small") + } + + peeringResponse := &PeeringResponse{ + Type: data[PEERING_RESPONSE_MARSHALLED_TYPE_START], + Peers: make([]*Peer, 0), + } + + if unmarshalledPeer, err := UnmarshalPeer(data[PEERING_RESPONSE_MARSHALLED_ISSUER_START:PEERING_RESPONSE_MARSHALLED_ISSUER_END]); err != nil { + return nil, err + } else { + peeringResponse.Issuer = unmarshalledPeer + } + + for i := 0; i < PEERING_RESPONSE_MARSHALLED_PEERS_AMOUNT; i++ { + PEERING_RESPONSE_MARSHALLED_PEER_START := PEERING_RESPONSE_MARSHALLED_PEERS_START + (i * PEERING_RESPONSE_MARSHALLED_PEER_SIZE) + PEERING_RESPONSE_MARSHALLED_PEER_END := PEERING_RESPONSE_MARSHALLED_PEER_START + PEERING_RESPONSE_MARSHALLED_PEER_SIZE + + if data[PEERING_RESPONSE_MARSHALLED_PEER_START] == 1 { + peer, err := UnmarshalPeer(data[PEERING_RESPONSE_MARSHALLED_PEER_START+1 : PEERING_RESPONSE_MARSHALLED_PEER_END]) + if err != nil { + return nil, err + } + + peeringResponse.Peers = append(peeringResponse.Peers, peer) + } + } + + if issuer, err := identity.FromSignedData(data[:PEERING_RESPONSE_MARSHALLED_SIGNATURE_START], data[PEERING_RESPONSE_MARSHALLED_SIGNATURE_START:]); err != nil { + return nil, err + } else { + if !bytes.Equal(issuer.Identifier, peeringResponse.Issuer.Identity.Identifier) { + return nil, ErrInvalidSignature + } + } + copy(peeringResponse.Signature[:], data[PEERING_RESPONSE_MARSHALLED_SIGNATURE_START:PEERING_RESPONSE_MARSHALLED_SIGNATURE_END]) + + return peeringResponse, nil +} + +func (this *PeeringResponse) Sign() *PeeringResponse { + dataToSign := this.Marshal()[:PEERING_RESPONSE_MARSHALLED_SIGNATURE_START] + if signature, err := this.Issuer.Identity.Sign(dataToSign); err != nil { + panic(err) + } else { + copy(this.Signature[:], signature) + } + + return this +} + +func (this *PeeringResponse) Marshal() []byte { + result := make([]byte, PEERING_RESPONSE_MARSHALLED_TOTAL_SIZE) + + result[PEERING_RESPONSE_MARSHALLED_TYPE_START] = this.Type + + copy(result[PEERING_RESPONSE_MARSHALLED_ISSUER_START:PEERING_RESPONSE_MARSHALLED_ISSUER_END], this.Issuer.Marshal()) + + for i, peer := range this.Peers { + PEERING_RESPONSE_MARSHALLED_PEER_START := PEERING_RESPONSE_MARSHALLED_PEERS_START + (i * PEERING_RESPONSE_MARSHALLED_PEER_SIZE) + PEERING_RESPONSE_MARSHALLED_PEER_END := PEERING_RESPONSE_MARSHALLED_PEER_START + PEERING_RESPONSE_MARSHALLED_PEER_SIZE + + result[PEERING_RESPONSE_MARSHALLED_PEER_START] = 1 + copy(result[PEERING_RESPONSE_MARSHALLED_PEER_START+1:PEERING_RESPONSE_MARSHALLED_PEER_END], peer.Marshal()[:PEERING_RESPONSE_MARSHALLED_PEER_SIZE-1]) + } + + copy(result[PEERING_RESPONSE_MARSHALLED_SIGNATURE_START:PEERING_RESPONSE_MARSHALLED_SIGNATURE_END], this.Signature[:PEERING_RESPONSE_MARSHALLED_SIGNATURE_SIZE]) + + return result +} diff --git a/plugins/autopeering/protocol/types.go b/plugins/autopeering/protocol/types.go new file mode 100644 index 0000000000000000000000000000000000000000..22a7f44ea0c7d6dd34cda08f0429ae0ef626e3cb --- /dev/null +++ b/plugins/autopeering/protocol/types.go @@ -0,0 +1,7 @@ +package protocol + +type ProtocolType = byte + +type AddressType = byte + +type ResponseType = byte diff --git a/plugins/autopeering/salt/constants.go b/plugins/autopeering/salt/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..6c5d016d590f6b142ed1f93331d7d827f8978451 --- /dev/null +++ b/plugins/autopeering/salt/constants.go @@ -0,0 +1,14 @@ +package salt + +const ( + SALT_BYTES_SIZE = 20 + SALT_TIME_SIZE = 15 + + SALT_BYTES_START = 0 + SALT_TIME_START = SALT_BYTES_END + + SALT_BYTES_END = SALT_BYTES_START + SALT_BYTES_SIZE + SALT_TIME_END = SALT_TIME_START + SALT_TIME_SIZE + + SALT_MARSHALLED_SIZE = SALT_TIME_END +) diff --git a/plugins/autopeering/salt/salt.go b/plugins/autopeering/salt/salt.go new file mode 100644 index 0000000000000000000000000000000000000000..bc1555de1360bdeff87a101539ad58dc41f6b0d3 --- /dev/null +++ b/plugins/autopeering/salt/salt.go @@ -0,0 +1,56 @@ +package salt + +import ( + "crypto/rand" + "github.com/pkg/errors" + "time" +) + +type Salt struct { + Bytes []byte + ExpirationTime time.Time +} + +func New(lifetime time.Duration) *Salt { + salt := &Salt{ + Bytes: make([]byte, SALT_BYTES_SIZE), + ExpirationTime: time.Now().Add(lifetime), + } + + if _, err := rand.Read(salt.Bytes); err != nil { + panic(err) + } + + return salt +} + +func Unmarshal(marshalledSalt []byte) (*Salt, error) { + if len(marshalledSalt) < SALT_MARSHALLED_SIZE { + return nil, errors.New("marshalled salt bytes not long enough") + } + + salt := &Salt{ + Bytes: make([]byte, SALT_BYTES_SIZE), + } + copy(salt.Bytes, marshalledSalt[SALT_BYTES_START:SALT_BYTES_END]) + + if err := salt.ExpirationTime.UnmarshalBinary(marshalledSalt[SALT_TIME_START:SALT_TIME_END]); err != nil { + return nil, err + } + + return salt, nil +} + +func (this *Salt) Marshal() []byte { + result := make([]byte, SALT_BYTES_SIZE+SALT_TIME_SIZE) + + copy(result[SALT_BYTES_START:SALT_BYTES_END], this.Bytes) + + if bytes, err := this.ExpirationTime.MarshalBinary(); err != nil { + panic(err) + } else { + copy(result[SALT_TIME_START:SALT_TIME_END], bytes) + } + + return result +} \ No newline at end of file diff --git a/plugins/autopeering/saltmanager/constants.go b/plugins/autopeering/saltmanager/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..12c19170ebe4b67a79172d0d69fcced9e867355a --- /dev/null +++ b/plugins/autopeering/saltmanager/constants.go @@ -0,0 +1,13 @@ +package saltmanager + +import "time" + +const ( + PUBLIC_SALT_LIFETIME = 50 * time.Second + PRIVATE_SALT_LIFETIME = 50 * time.Second +) + +var ( + PUBLIC_SALT_SETTINGS_KEY = []byte("PUBLIC_SALT") + PRIVATE_SALT_SETTINGS_KEY = []byte("PRIVATE_SALT") +) diff --git a/plugins/autopeering/saltmanager/events.go b/plugins/autopeering/saltmanager/events.go new file mode 100644 index 0000000000000000000000000000000000000000..a1bc3a8d3afd497dab5e389eb7537e13db975636 --- /dev/null +++ b/plugins/autopeering/saltmanager/events.go @@ -0,0 +1,34 @@ +package saltmanager + +import ( + "github.com/iotaledger/goshimmer/plugins/autopeering/salt" + "reflect" +) + +type packageEvents struct { + UpdatePublicSalt *saltEvent + UpdatePrivateSalt *saltEvent +} + +type saltEvent struct { + callbacks map[uintptr]SaltConsumer +} + +func (this *saltEvent) Attach(callback SaltConsumer) { + this.callbacks[reflect.ValueOf(callback).Pointer()] = callback +} + +func (this *saltEvent) Detach(callback SaltConsumer) { + delete(this.callbacks, reflect.ValueOf(callback).Pointer()) +} + +func (this *saltEvent) Trigger(salt *salt.Salt) { + for _, callback := range this.callbacks { + callback(salt) + } +} + +var Events = packageEvents{ + UpdatePublicSalt: &saltEvent{make(map[uintptr]SaltConsumer)}, + UpdatePrivateSalt: &saltEvent{make(map[uintptr]SaltConsumer)}, +} diff --git a/plugins/autopeering/saltmanager/saltmanager.go b/plugins/autopeering/saltmanager/saltmanager.go new file mode 100644 index 0000000000000000000000000000000000000000..c119d3b23c5a06aad6274dab3dff368a17353220 --- /dev/null +++ b/plugins/autopeering/saltmanager/saltmanager.go @@ -0,0 +1,81 @@ +package saltmanager + +import ( + "github.com/dgraph-io/badger" + "github.com/iotaledger/goshimmer/packages/daemon" + "github.com/iotaledger/goshimmer/packages/settings" + "github.com/iotaledger/goshimmer/plugins/autopeering/salt" + "time" +) + +var ( + PRIVATE_SALT = createSalt(PRIVATE_SALT_SETTINGS_KEY, PRIVATE_SALT_LIFETIME, Events.UpdatePrivateSalt.Trigger) + PUBLIC_SALT = createSalt(PUBLIC_SALT_SETTINGS_KEY, PUBLIC_SALT_LIFETIME, Events.UpdatePublicSalt.Trigger) +) + +func generateNewSalt(key []byte, lifetime time.Duration) *salt.Salt { + newSalt := salt.New(lifetime) + + if err := settings.Set(key, newSalt.Marshal()); err != nil { + panic(err) + } + + return newSalt +} + +func getSalt(key []byte, lifetime time.Duration) *salt.Salt { + saltBytes, err := settings.Get(key) + if err != nil { + if err == badger.ErrKeyNotFound { + return generateNewSalt(key, lifetime) + } else { + panic(err) + } + } + + if resultingSalt, err := salt.Unmarshal(saltBytes); err != nil { + panic(err) + } else { + return resultingSalt + } +} + +func updatePublicSalt(saltToUpdate *salt.Salt, settingsKey []byte, lifeSpan time.Duration, updateCallback func(salt *salt.Salt)) { + newSalt := salt.New(lifeSpan) + + saltToUpdate.Bytes = newSalt.Bytes + saltToUpdate.ExpirationTime = newSalt.ExpirationTime + + if err := settings.Set(settingsKey, saltToUpdate.Marshal()); err != nil { + panic(err) + } + + updateCallback(saltToUpdate) + + scheduleUpdateForSalt(saltToUpdate, settingsKey, lifeSpan, updateCallback) +} + +func scheduleUpdateForSalt(saltToUpdate *salt.Salt, settingsKey []byte, lifeSpan time.Duration, callback SaltConsumer) { + now := time.Now() + + if saltToUpdate.ExpirationTime.Before(now) { + updatePublicSalt(saltToUpdate, settingsKey, lifeSpan, callback) + } else { + go func() { + select { + case <-daemon.ShutdownSignal: + return + case <-time.After(saltToUpdate.ExpirationTime.Sub(now)): + updatePublicSalt(saltToUpdate, settingsKey, lifeSpan, callback) + } + }() + } +} + +func createSalt(settingsKey []byte, lifeSpan time.Duration, updateCallback SaltConsumer) *salt.Salt { + newSalt := getSalt(settingsKey, lifeSpan) + + scheduleUpdateForSalt(newSalt, settingsKey, lifeSpan, updateCallback) + + return newSalt +} diff --git a/plugins/autopeering/saltmanager/types.go b/plugins/autopeering/saltmanager/types.go new file mode 100644 index 0000000000000000000000000000000000000000..650741e2e188ce605d0b6290f7f75076866c1ec2 --- /dev/null +++ b/plugins/autopeering/saltmanager/types.go @@ -0,0 +1,5 @@ +package saltmanager + +import "github.com/iotaledger/goshimmer/plugins/autopeering/salt" + +type SaltConsumer = func(salt *salt.Salt) diff --git a/plugins/autopeering/server/constants.go b/plugins/autopeering/server/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..c122957ba31c3e7c99c1996ce7ce70cf891e4cab --- /dev/null +++ b/plugins/autopeering/server/constants.go @@ -0,0 +1,7 @@ +package server + +import "time" + +const ( + TCP_IDLE_TIMEOUT = 5 * time.Second +) diff --git a/plugins/autopeering/server/events.go b/plugins/autopeering/server/events.go new file mode 100644 index 0000000000000000000000000000000000000000..734a280756b2e5a0416236178198fd883c40e8e5 --- /dev/null +++ b/plugins/autopeering/server/events.go @@ -0,0 +1,74 @@ +package server + +import ( + "github.com/iotaledger/goshimmer/packages/network" + "github.com/iotaledger/goshimmer/plugins/autopeering/protocol" + "net" + "reflect" +) + +type pluginEvents struct { + ReceivePeeringRequest *addressPeeringRequestEvent + ReceiveTCPPeeringRequest *tcpPeeringRequestEvent + Error *ipErrorEvent +} + +type tcpPeeringRequestEvent struct { + callbacks map[uintptr]ConnectionPeeringRequestConsumer +} + +func (this *tcpPeeringRequestEvent) Attach(callback ConnectionPeeringRequestConsumer) { + this.callbacks[reflect.ValueOf(callback).Pointer()] = callback +} + +func (this *tcpPeeringRequestEvent) Detach(callback ConnectionPeeringRequestConsumer) { + delete(this.callbacks, reflect.ValueOf(callback).Pointer()) +} + +func (this *tcpPeeringRequestEvent) Trigger(conn network.Connection, request *protocol.PeeringRequest) { + for _, callback := range this.callbacks { + callback(conn, request) + } +} + +type addressPeeringRequestEvent struct { + callbacks map[uintptr]IPPeeringRequestConsumer +} + +func (this *addressPeeringRequestEvent) Attach(callback IPPeeringRequestConsumer) { + this.callbacks[reflect.ValueOf(callback).Pointer()] = callback +} + +func (this *addressPeeringRequestEvent) Detach(callback IPPeeringRequestConsumer) { + delete(this.callbacks, reflect.ValueOf(callback).Pointer()) +} + +func (this *addressPeeringRequestEvent) Trigger(ip net.IP, request *protocol.PeeringRequest) { + for _, callback := range this.callbacks { + callback(ip, request) + } +} + +type ipErrorEvent struct { + callbacks map[uintptr]IPErrorConsumer +} + +func (this *ipErrorEvent) Attach(callback IPErrorConsumer) { + this.callbacks[reflect.ValueOf(callback).Pointer()] = callback +} + +func (this *ipErrorEvent) Detach(callback IPErrorConsumer) { + delete(this.callbacks, reflect.ValueOf(callback).Pointer()) +} + +func (this *ipErrorEvent) Trigger(ip net.IP, err error) { + for _, callback := range this.callbacks { + callback(ip, err) + } +} + +var Events = &pluginEvents{ + ReceivePeeringRequest: &addressPeeringRequestEvent{make(map[uintptr]IPPeeringRequestConsumer)}, + ReceiveTCPPeeringRequest: &tcpPeeringRequestEvent{make(map[uintptr]ConnectionPeeringRequestConsumer)}, + Error: &ipErrorEvent{make(map[uintptr]IPErrorConsumer)}, +} diff --git a/plugins/autopeering/server/server.go b/plugins/autopeering/server/server.go new file mode 100644 index 0000000000000000000000000000000000000000..3615391bd0044eabf5c1e459c449c0300b813906 --- /dev/null +++ b/plugins/autopeering/server/server.go @@ -0,0 +1,20 @@ +package server + +import ( + "github.com/iotaledger/goshimmer/packages/node" +) + +func Configure(plugin *node.Plugin) { + ConfigureUDPServer(plugin) + ConfigureTCPServer(plugin) +} + +func Run(plugin *node.Plugin) { + RunUDPServer(plugin) + RunTCPServer(plugin) +} + +func Shutdown(plugin *node.Plugin) { + ShutdownUDPServer(plugin) + ShutdownTCPServer(plugin) +} diff --git a/plugins/autopeering/server/tcp_server.go b/plugins/autopeering/server/tcp_server.go new file mode 100644 index 0000000000000000000000000000000000000000..266870b142bc0abc9684a48f7854788c4d59af6d --- /dev/null +++ b/plugins/autopeering/server/tcp_server.go @@ -0,0 +1,73 @@ +package server + +import ( + "github.com/iotaledger/goshimmer/packages/daemon" + "github.com/iotaledger/goshimmer/packages/network" + "github.com/iotaledger/goshimmer/packages/network/tcp" + "github.com/iotaledger/goshimmer/packages/node" + "github.com/iotaledger/goshimmer/plugins/autopeering/parameters" + "github.com/iotaledger/goshimmer/plugins/autopeering/protocol" + "math" + "net" + "strconv" +) + +var tcpServer = tcp.NewServer() + +func ConfigureTCPServer(plugin *node.Plugin) { + tcpServer.Events.Connect.Attach(func(peer network.Connection) { + receivedData := make([]byte, protocol.PEERING_REQUEST_MARSHALLED_TOTAL_SIZE) + + peer.SetTimeout(TCP_IDLE_TIMEOUT) + + offset := 0 + peer.OnReceiveData(func(data []byte) { + remainingCapacity := int(math.Min(float64(protocol.PEERING_REQUEST_MARSHALLED_TOTAL_SIZE- offset), float64(len(data)))) + + copy(receivedData[offset:], data[:remainingCapacity]) + offset += len(data) + + if offset >= protocol.PEERING_REQUEST_MARSHALLED_TOTAL_SIZE { + if peeringRequest, err := protocol.UnmarshalPeeringRequest(receivedData); err != nil { + Events.Error.Trigger(peer.GetConnection().RemoteAddr().(*net.TCPAddr).IP, err) + } else { + Events.ReceiveTCPPeeringRequest.Trigger(peer, peeringRequest) + } + } + }) + + go peer.HandleConnection() + }) + + tcpServer.Events.Error.Attach(func(err error) { + plugin.LogFailure(err.Error()) + }) + tcpServer.Events.Start.Attach(func() { + if *parameters.ADDRESS.Value == "0.0.0.0" { + plugin.LogSuccess("Starting TCP Server (port " + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ... done") + } else { + plugin.LogSuccess("Starting TCP Server (" + *parameters.ADDRESS.Value + ":" + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ... done") + } + }) + tcpServer.Events.Shutdown.Attach(func() { + plugin.LogSuccess("Stopping TCP Server ... done") + }) +} + +func RunTCPServer(plugin *node.Plugin) { + daemon.BackgroundWorker(func() { + if *parameters.ADDRESS.Value == "0.0.0.0" { + plugin.LogInfo("Starting TCP Server (port " + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ...") + } else { + plugin.LogInfo("Starting TCP Server (" + *parameters.ADDRESS.Value + ":" + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ...") + } + + tcpServer.Listen(*parameters.UDP_PORT.Value) + }) +} + +func ShutdownTCPServer(plugin *node.Plugin) { + plugin.LogInfo("Stopping TCP Server ...") + + tcpServer.Shutdown() +} diff --git a/plugins/autopeering/server/types.go b/plugins/autopeering/server/types.go new file mode 100644 index 0000000000000000000000000000000000000000..b368652ef58d97ed84f9fff29e409ca0e88cfbbf --- /dev/null +++ b/plugins/autopeering/server/types.go @@ -0,0 +1,13 @@ +package server + +import ( + "github.com/iotaledger/goshimmer/packages/network" + "github.com/iotaledger/goshimmer/plugins/autopeering/protocol" + "net" +) + +type ConnectionPeeringRequestConsumer = func(conn network.Connection, request *protocol.PeeringRequest) + +type IPPeeringRequestConsumer = func(ip net.IP, request *protocol.PeeringRequest) + +type IPErrorConsumer = func(ip net.IP, err error) diff --git a/plugins/autopeering/server/udp_server.go b/plugins/autopeering/server/udp_server.go new file mode 100644 index 0000000000000000000000000000000000000000..89e729b632a4fad87d7a654680cb64ebfd9ae639 --- /dev/null +++ b/plugins/autopeering/server/udp_server.go @@ -0,0 +1,62 @@ +package server + +import ( + "github.com/iotaledger/goshimmer/packages/daemon" + "github.com/iotaledger/goshimmer/packages/network/udp" + "github.com/iotaledger/goshimmer/packages/node" + "github.com/iotaledger/goshimmer/plugins/autopeering/parameters" + "github.com/iotaledger/goshimmer/plugins/autopeering/protocol" + "net" + "strconv" +) + +var udpServer = udp.NewServer() + +func ConfigureUDPServer(plugin *node.Plugin) { + Events.Error.Attach(func(ip net.IP, err error) { + plugin.LogFailure(err.Error()) + }) + + udpServer.Events.ReceiveData.Attach(processReceivedData) + udpServer.Events.Error.Attach(func(err error) { + plugin.LogFailure(err.Error()) + }) + udpServer.Events.Start.Attach(func() { + if *parameters.ADDRESS.Value == "0.0.0.0" { + plugin.LogSuccess("Starting UDP Server (port " + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ... done") + } else { + plugin.LogSuccess("Starting UDP Server (" + *parameters.ADDRESS.Value + ":" + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ... done") + } + }) + udpServer.Events.Shutdown.Attach(func() { + plugin.LogSuccess("Stopping UDP Server ... done") + }) +} + +func RunUDPServer(plugin *node.Plugin) { + daemon.BackgroundWorker(func() { + if *parameters.ADDRESS.Value == "0.0.0.0" { + plugin.LogInfo("Starting UDP Server (port " + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ...") + } else { + plugin.LogInfo("Starting UDP Server (" + *parameters.ADDRESS.Value + ":" + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ...") + } + + udpServer.Listen(*parameters.ADDRESS.Value, *parameters.UDP_PORT.Value) + }) +} + +func ShutdownUDPServer(plugin *node.Plugin) { + plugin.LogInfo("Stopping UDP Server ...") + + udpServer.Shutdown() +} + +func processReceivedData(addr *net.UDPAddr, data []byte) { + + if peeringRequest, err := protocol.UnmarshalPeeringRequest(data); err != nil { + Events.Error.Trigger(addr.IP, err) + } else { + Events.ReceivePeeringRequest.Trigger(addr.IP, peeringRequest) + } +} + diff --git a/plugins/cli/cli.go b/plugins/cli/cli.go new file mode 100644 index 0000000000000000000000000000000000000000..5676ae7269da776d04f1c4c1e64e2b1c15627392 --- /dev/null +++ b/plugins/cli/cli.go @@ -0,0 +1,34 @@ +package cli + +import ( + "flag" + "fmt" + "os" + "path/filepath" +) + +func AddIntParameter(p *int, name string, usage string) { + flag.IntVar(p, name, *p, usage) +} + +func AddStringParameter(p *string, name string, usage string) { + flag.StringVar(p, name, *p, usage) +} + +func printUsage() { + _, err := fmt.Fprintf( + os.Stderr, + "\n" + + "SHIMMER 1.0\n\n" + + " A lightweight modular IOTA node.\n\n" + + "Usage:\n\n" + + " %s [OPTIONS]\n\n" + + "Options:\n\n", + filepath.Base(os.Args[0]), + ) + if err != nil { + panic(err) + } + + flag.PrintDefaults() +} diff --git a/plugins/cli/plugin.go b/plugins/cli/plugin.go new file mode 100644 index 0000000000000000000000000000000000000000..fd3121b753196342d6ec3b4fa0a7d954bc31ff56 --- /dev/null +++ b/plugins/cli/plugin.go @@ -0,0 +1,43 @@ +package cli + +import ( + "flag" + "github.com/iotaledger/goshimmer/packages/parameter" + "github.com/iotaledger/goshimmer/packages/node" + "strings" +) + +func onAddIntParameter(param *parameter.IntParameter) { + flagName := strings.Replace(strings.Replace(strings.ToLower(param.Name), "/", "-", 1), "_", "-", -1) + + AddIntParameter(param.Value, flagName, param.Description) +} + +func onAddStringParameter(param *parameter.StringParameter) { + flagName := strings.Replace(strings.Replace(strings.ToLower(param.Name), "/", "-", 1), "_", "-", -1) + + AddStringParameter(param.Value, flagName, param.Description) +} + +func init() { + for _, param := range parameter.GetInts() { + onAddIntParameter(param) + } + + for _, param := range parameter.GetStrings() { + onAddStringParameter(param) + } + + parameter.Events.AddInt.Attach(onAddIntParameter) + parameter.Events.AddString.Attach(onAddStringParameter) + + flag.Usage = printUsage + + flag.Parse() +} + +func configure(ctx *node.Plugin) {} + +func run(plugin *node.Plugin) {} + +var PLUGIN = node.NewPlugin("CLI", configure) diff --git a/plugins/gracefulshutdown/constants.go b/plugins/gracefulshutdown/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..e63ac569efe5b0df58f2521503eae105c7f874e1 --- /dev/null +++ b/plugins/gracefulshutdown/constants.go @@ -0,0 +1,4 @@ +package gracefulshutdown + +// The maximum amount of time we wait for background processes to terminate. After that the process is killed. +const WAIT_TO_KILL_TIME_IN_SECONDS = 10 diff --git a/plugins/gracefulshutdown/plugin.go b/plugins/gracefulshutdown/plugin.go new file mode 100644 index 0000000000000000000000000000000000000000..cd6bc693a850164978fc42378e417b07dfdea76c --- /dev/null +++ b/plugins/gracefulshutdown/plugin.go @@ -0,0 +1,42 @@ +package gracefulshutdown + +import ( + "github.com/iotaledger/goshimmer/packages/daemon" + "github.com/iotaledger/goshimmer/packages/node" + "os" + "os/signal" + "strconv" + "syscall" + "time" +) + +func run(plugin *node.Plugin) { + gracefulStop := make(chan os.Signal) + signal.Notify(gracefulStop, syscall.SIGTERM) + signal.Notify(gracefulStop, syscall.SIGINT) + + go func() { + <- gracefulStop + + plugin.LogWarning("Received shutdown request - waiting (max " + strconv.Itoa(WAIT_TO_KILL_TIME_IN_SECONDS) + " seconds) to finish processing ...") + + go func() { + start := time.Now() + for x := range time.Tick(1 * time.Second) { + secondsSinceStart := x.Sub(start).Seconds() + + if secondsSinceStart <= WAIT_TO_KILL_TIME_IN_SECONDS { + plugin.LogWarning("Received shutdown request - waiting (max " + strconv.Itoa(WAIT_TO_KILL_TIME_IN_SECONDS - int(secondsSinceStart)) + " seconds) to finish processing ...") + } else { + plugin.LogFailure("The background processes did not terminate in time! Forcing shutdown ...") + + os.Exit(1) + } + } + }() + + daemon.Shutdown() + }() +} + +var PLUGIN = node.NewPlugin("Graceful Shutdown", run) diff --git a/plugins/statusscreen/constants.go b/plugins/statusscreen/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..d34a8a8ac4fe849d8706704a7d15ef96dedf1921 --- /dev/null +++ b/plugins/statusscreen/constants.go @@ -0,0 +1,7 @@ +package statusscreen + +import "time" + +const ( + REPAINT_INTERVAL = 500 * time.Millisecond +) diff --git a/plugins/statusscreen/logger.go b/plugins/statusscreen/logger.go new file mode 100644 index 0000000000000000000000000000000000000000..5fd4b29a57af173a1c245901e10e06369b693612 --- /dev/null +++ b/plugins/statusscreen/logger.go @@ -0,0 +1,44 @@ +package statusscreen + +import ( + "github.com/iotaledger/goshimmer/packages/node" + "time" +) + +func storeStatusMessage(pluginName string, message string, logLevel int) { + messageLog = append(messageLog, &StatusMessage{ + Source: pluginName, + LogLevel: logLevel, + Message: message, + Time: time.Now(), + }) + + if statusMessage, exists := statusMessages[pluginName]; !exists { + statusMessages[pluginName] = &StatusMessage{ + Source: pluginName, + LogLevel: logLevel, + Message: message, + Time: time.Now(), + } + } else { + statusMessage.LogLevel = logLevel + statusMessage.Message = message + statusMessage.Time = time.Now() + } +} + +var DEFAULT_LOGGER = &node.Logger{ + Enabled: true, + LogInfo: func(pluginName string, message string) { + storeStatusMessage(pluginName, message, node.LOG_LEVEL_INFO) + }, + LogSuccess: func(pluginName string, message string) { + storeStatusMessage(pluginName, message, node.LOG_LEVEL_SUCCESS) + }, + LogWarning: func(pluginName string, message string) { + storeStatusMessage(pluginName, message, node.LOG_LEVEL_WARNING) + }, + LogFailure: func(pluginName string, message string) { + storeStatusMessage(pluginName, message, node.LOG_LEVEL_FAILURE) + }, +} diff --git a/plugins/statusscreen/status_message.go b/plugins/statusscreen/status_message.go new file mode 100644 index 0000000000000000000000000000000000000000..5f2a9ecdf058ed962104802be6b90ea8ea6a9b67 --- /dev/null +++ b/plugins/statusscreen/status_message.go @@ -0,0 +1,10 @@ +package statusscreen + +import "time" + +type StatusMessage struct { + Source string + LogLevel int + Message string + Time time.Time +} diff --git a/plugins/statusscreen/statusscreen.go b/plugins/statusscreen/statusscreen.go new file mode 100644 index 0000000000000000000000000000000000000000..df4e4f717fba2a1490c6859f919861bd0565bc91 --- /dev/null +++ b/plugins/statusscreen/statusscreen.go @@ -0,0 +1,119 @@ +package statusscreen + +import ( + "github.com/gdamore/tcell" + "github.com/iotaledger/goshimmer/packages/daemon" + "github.com/iotaledger/goshimmer/packages/node" + "github.com/rivo/tview" + "time" +) + +var statusMessages = make(map[string]*StatusMessage) +var messageLog = make([]*StatusMessage, 0) + +var app *tview.Application + +func configure(plugin *node.Plugin) { + node.DEFAULT_LOGGER.Enabled = false + + plugin.Node.AddLogger(DEFAULT_LOGGER) + + daemon.Events.Shutdown.Attach(func() { + node.DEFAULT_LOGGER.Enabled = true + + if app != nil { + app.Stop() + } + }) +} + +func run(plugin *node.Plugin) { + newPrimitive := func(text string) *tview.TextView { + textView := tview.NewTextView() + + textView. + SetTextAlign(tview.AlignLeft). + SetText(" " + text) + + return textView + } + + app = tview.NewApplication() + + headerBar := NewUIHeaderBar() + + content := tview.NewGrid() + content.SetBackgroundColor(tcell.ColorWhite) + content.SetColumns(0) + + footer := newPrimitive("") + footer.SetBackgroundColor(tcell.ColorDarkMagenta) + footer.SetTextColor(tcell.ColorWhite) + + grid := tview.NewGrid(). + SetRows(10, 0, 1). + SetColumns(0). + SetBorders(false). + AddItem(headerBar.Primitive, 0, 0, 1, 1, 0, 0, false). + AddItem(content, 1, 0, 1, 1, 0, 0, false). + AddItem(footer, 2, 0, 1, 1, 0, 0, false) + + frame := tview.NewFrame(grid). + SetBorders(1, 1, 0, 0, 2, 2) + frame.SetBackgroundColor(tcell.ColorDarkGray) + + app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyCtrlC || event.Key() == tcell.KeyESC{ + daemon.Shutdown() + + return nil + } + + return event + }) + + app.SetBeforeDrawFunc(func(screen tcell.Screen) bool { + headerBar.Update() + + rows := make([]int, 1) + rows[0] = 1 + _, _, _, height := content.GetRect() + for i := 0; i < len(messageLog) && i < height - 2; i++ { + rows = append(rows, 1) + } + + content.Clear() + content.SetRows(rows...) + + blankLine := newPrimitive("") + blankLine.SetBackgroundColor(tcell.ColorWhite) + content.AddItem(blankLine, 0, 0, 1, 1, 0, 0, false) + + for i, message := range messageLog[len(messageLog) - len(rows) - 1 + 2:] { + if i < height - 2 { + content.AddItem(NewUILogEntry(*message).Primitive, i+1, 0, 1, 1, 0, 0, false) + } + } + + return false + }) + + daemon.BackgroundWorker(func() { + for { + select { + case <- daemon.ShutdownSignal: + return + case <- time.After(1 * time.Second): + app.QueueUpdateDraw(func() {}) + } + } + }) + + daemon.BackgroundWorker(func() { + if err := app.SetRoot(frame, true).SetFocus(frame).Run(); err != nil { + panic(err) + } + }) +} + +var PLUGIN = node.NewPlugin("Status Screen", configure, run) diff --git a/plugins/statusscreen/ui_header_bar.go b/plugins/statusscreen/ui_header_bar.go new file mode 100644 index 0000000000000000000000000000000000000000..0315e24fb8133cda0a72dc4c1947042a4f44b9f0 --- /dev/null +++ b/plugins/statusscreen/ui_header_bar.go @@ -0,0 +1,95 @@ +package statusscreen + +import ( + "fmt" + "github.com/gdamore/tcell" + "github.com/iotaledger/goshimmer/packages/accountability" + "github.com/iotaledger/goshimmer/plugins/autopeering/peermanager" + "github.com/rivo/tview" + "strconv" + "time" +) + +var start = time.Now() + +type UIHeaderBar struct { + Primitive *tview.Grid + LogoContainer *tview.TextView + InfoContainer *tview.TextView +} + +func NewUIHeaderBar() *UIHeaderBar { + headerBar := &UIHeaderBar{ + Primitive: tview.NewGrid(), + LogoContainer: tview.NewTextView(), + InfoContainer: tview.NewTextView(), + } + + headerBar.LogoContainer. + SetTextAlign(tview.AlignLeft). + SetTextColor(tcell.ColorWhite). + SetDynamicColors(true). + SetBackgroundColor(tcell.ColorDarkMagenta) + + headerBar.InfoContainer. + SetTextAlign(tview.AlignRight). + SetTextColor(tcell.ColorWhite). + SetDynamicColors(true). + SetBackgroundColor(tcell.ColorDarkMagenta) + + headerBar.Primitive. + SetColumns(17, 0). + SetRows(0). + SetBorders(false). + AddItem(headerBar.LogoContainer, 0, 0, 1, 1, 0, 0, false). + AddItem(headerBar.InfoContainer, 0, 1, 1, 1, 0, 0, false) + + + headerBar.printLogo() + headerBar.Update() + + return headerBar +} + +func (headerBar *UIHeaderBar) Update() { + duration := time.Now().Sub(start) + + headerBar.InfoContainer.Clear() + + fmt.Fprintln(headerBar.InfoContainer) + fmt.Fprintln(headerBar.InfoContainer, "[::d]COO-LESS IOTA PROTOTYPE - [::b]Status: [green::b]SYNCED ") + fmt.Fprintln(headerBar.InfoContainer) + fmt.Fprintln(headerBar.InfoContainer) + fmt.Fprintln(headerBar.InfoContainer) + fmt.Fprintln(headerBar.InfoContainer) + fmt.Fprintln(headerBar.InfoContainer, "[::b]Node Identifier: [::d]" + accountability.OWN_ID.StringIdentifier + " ") + fmt.Fprintln(headerBar.InfoContainer, "[::b]Known Peers: [::d]" + strconv.Itoa(len(peermanager.KNOWN_PEERS.Peers)) + " ") + fmt.Fprintf(headerBar.InfoContainer, "[::b]Uptime: [::d]"); + + if int(duration.Seconds()) / (60 * 60 * 24) > 0 { + // d + fmt.Fprintf(headerBar.InfoContainer, "%02dd ", int(duration.Hours()) / 24) + } + + if int(duration.Seconds()) / (60 * 60) > 0 { + fmt.Fprintf(headerBar.InfoContainer, "%02dh ", int(duration.Hours()) % 24) + } + + if int(duration.Seconds()) / 60 > 0 { + fmt.Fprintf(headerBar.InfoContainer, "%02dm ", int(duration.Minutes()) % 60) + } + + fmt.Fprintf(headerBar.InfoContainer, "%02ds ", int(duration.Seconds()) % 60) +} + +func (headerBar *UIHeaderBar) printLogo() { + fmt.Fprintln(headerBar.LogoContainer, "") + fmt.Fprintln(headerBar.LogoContainer, " SHIMMER 1.0.0") + fmt.Fprintln(headerBar.LogoContainer, " ┏━━━━━━┳━━━━━━┓") + fmt.Fprintln(headerBar.LogoContainer, " ━━━┓ ┃ ┏━━━") + fmt.Fprintln(headerBar.LogoContainer, " ┓ ┃ ┃ ┃ ┏") + fmt.Fprintln(headerBar.LogoContainer, " ┃ ┗ ┃ ┛ ┃") + fmt.Fprintln(headerBar.LogoContainer, " ┗ ┏ ┃ ┓ ┛") + fmt.Fprintln(headerBar.LogoContainer, " ┃ ┃ ┃") + fmt.Fprintln(headerBar.LogoContainer, " ┻") +} diff --git a/plugins/statusscreen/ui_log.go b/plugins/statusscreen/ui_log.go new file mode 100644 index 0000000000000000000000000000000000000000..3f0826aee15b914d1949b3b85a49c6d6e5301eca --- /dev/null +++ b/plugins/statusscreen/ui_log.go @@ -0,0 +1,15 @@ +package statusscreen + +import "github.com/rivo/tview" + +type UILog struct { + Primitive *tview.Grid +} + +func NewUILog() *UILog { + uiLog := &UILog{ + Primitive: tview.NewGrid(), + } + + return uiLog +} diff --git a/plugins/statusscreen/ui_log_entry.go b/plugins/statusscreen/ui_log_entry.go new file mode 100644 index 0000000000000000000000000000000000000000..0ee917dbefce20730c487e737ecc46c2216ff4d3 --- /dev/null +++ b/plugins/statusscreen/ui_log_entry.go @@ -0,0 +1,69 @@ +package statusscreen + +import ( + "fmt" + "github.com/gdamore/tcell" + "github.com/iotaledger/goshimmer/packages/node" + "github.com/rivo/tview" +) + +type UILogEntry struct { + Primitive *tview.Grid + TimeContainer *tview.TextView + MessageContainer *tview.TextView + LogLevelContainer *tview.TextView +} + +func NewUILogEntry(message StatusMessage) *UILogEntry { + logEntry := &UILogEntry{ + Primitive: tview.NewGrid(), + TimeContainer: tview.NewTextView(), + MessageContainer: tview.NewTextView(), + LogLevelContainer: tview.NewTextView(), + } + + logEntry.TimeContainer.SetBackgroundColor(tcell.ColorWhite) + logEntry.TimeContainer.SetTextColor(tcell.ColorBlack) + logEntry.TimeContainer.SetDynamicColors(true) + + logEntry.MessageContainer.SetBackgroundColor(tcell.ColorWhite) + logEntry.MessageContainer.SetTextColor(tcell.ColorBlack) + logEntry.MessageContainer.SetDynamicColors(true) + + logEntry.LogLevelContainer.SetBackgroundColor(tcell.ColorWhite) + logEntry.LogLevelContainer.SetTextColor(tcell.ColorBlack) + logEntry.LogLevelContainer.SetDynamicColors(true) + + textColor := "black" + switch message.LogLevel { + case node.LOG_LEVEL_INFO: + fmt.Fprintf(logEntry.LogLevelContainer, " [black::d][ [blue::d]INFO [black::d]]") + case node.LOG_LEVEL_SUCCESS: + fmt.Fprintf(logEntry.LogLevelContainer, " [black::d][ [green::d]OK [black::d]]") + case node.LOG_LEVEL_WARNING: + fmt.Fprintf(logEntry.LogLevelContainer, " [black::d][ [yellow::d]WARN [black::d]]") + + textColor = "yellow" + case node.LOG_LEVEL_FAILURE: + fmt.Fprintf(logEntry.LogLevelContainer, " [black::d][ [red::d]FAIL [black::d]]") + + textColor = "red" + } + + fmt.Fprintf(logEntry.TimeContainer, " [black::b]" + message.Time.Format("01/02/2006 03:04:05 PM")) + if message.Source == "Node" { + fmt.Fprintf(logEntry.MessageContainer, "[" + textColor + "::d]" + message.Message) + } else { + fmt.Fprintf(logEntry.MessageContainer, "[" + textColor + "::d]" + message.Source + ": " + message.Message) + } + + logEntry.Primitive. + SetColumns(25, 0, 11). + SetRows(1). + SetBorders(false). + AddItem(logEntry.TimeContainer, 0, 0, 1, 1, 0, 0, false). + AddItem(logEntry.MessageContainer, 0, 1, 1, 1, 0, 0, false). + AddItem(logEntry.LogLevelContainer, 0, 2, 1, 1, 0, 0, false) + + return logEntry +}