package chain
import (
"github.com/op/go-logging"
"gitlab.com/bertrandbenj/politikojj/model"
"gitlab.com/bertrandbenj/politikojj/storage"
"sync"
)
var log = logging.MustGetLogger("chain")
type UnpackedBlock struct {
Cid string
Block model.Block
Cratos model.Cratos
Demos model.Demos
Currency model.Currency
model.ObservableList
}
//type Blockchain map[string]model.Block
func (c UnpackedBlock) Majority() uint64 {
return c.Demos.Population/2 + 1
}
type WalkHandler func(block model.Block, cid string)
type BlockChainService struct {
Head UnpackedBlock
Store storage.Store
byIndex map[uint32]string
network string
lock *sync.RWMutex
}
func (cs *BlockChainService) GetHead() UnpackedBlock {
cs.lock.RLock()
defer cs.lock.RUnlock()
return cs.Head
}
func NewChain(store storage.Store, network string) *BlockChainService {
return &BlockChainService{
Store: store,
Head: UnpackedBlock{},
byIndex: make(map[uint32]string),
network: network,
lock: &sync.RWMutex{},
}
}
//func (cs *BlockChainService) HeadCid() string {
// return cs.Head.Cid
//}
func (cs *BlockChainService) HeadBlock() model.Block {
return cs.GetHead().Block
}
//func (cs *BlockChainService) HeadSet() *UnpackedBlock {
// return &cs.Head
//}
//
//func (cs BlockChainService) SignBlock(block *model.Block, key crypto.PrivateK) {
// s, _ := key.Sign(block.PartToSign())
// block.Signature = s
//}
func (cs BlockChainService) BackwardWalk(handler WalkHandler) {
cid := cs.Head.Cid
block := model.Block{}
for count := 0; count < 10; count++ {
found := cs.Store.GetObject(cid, &block)
if !found {
break
}
handler(block, cid)
cid = block.Previous.Link
}
}
func (cs *BlockChainService) ByIndex(id uint32) model.Block {
block := model.Block{}
_ = cs.Store.GetObject(cs.byIndex[id], &block)
return block
}
func (cs *BlockChainService) push(cid string, newBlock model.Block) {
//cs.blockchain[cid] = newBlock
cs.lock.Lock()
defer cs.lock.Unlock()
cs.Head.Cid = cid
cs.Head.Block = newBlock
var demos model.Demos
if cs.Store.GetObject(newBlock.Demos.Link, &demos) {
cs.Head.Demos = demos
}
var cratos model.Cratos
if cs.Store.GetObject(newBlock.Cratos.Link, &cratos) {
cs.Head.Cratos = cratos
}
var ccy model.Currency
if cs.Store.GetObject(newBlock.Currency.Link, &ccy) {
cs.Head.Currency = ccy
}
cs.Head.Block = newBlock
cs.byIndex[newBlock.Index] = cid
go cs.Store.UpdateHead(cid)
}
func (cs *BlockChainService) ChainBlock(newBlock model.Block) (model.Block, string, error) {
cid, err := cs.Store.PutObject(newBlock)
if err != nil {
log.Error("PutObject new block", err)
}
cs.push(cid, newBlock)
cs.Head.NotifyAll()
log.Noticef("\033[32m✓ Chained Block n°%d\033[0m %s", newBlock.Index, cid)
return newBlock, cid, err
}
package chain
import (
"gitlab.com/bertrandbenj/politikojj/crypto"
"gitlab.com/bertrandbenj/politikojj/model"
"gitlab.com/bertrandbenj/politikojj/utilities"
)
func MakeAmendment(name string, rules []model.Rule, key crypto.Keypair) model.Amendment {
var res = model.Amendment{
Name: name,
Predicates: rules,
}
StampAndSign(&res, key)
return res
}
func MakeClaim(name string, role string, key crypto.Keypair) model.Claim {
var res = model.Claim{
ChosenName: name,
Role: role,
}
StampAndSign(&res, key)
return res
}
// StampAndSign the object using the given private key
func StampAndSign(obj model.Signable, key crypto.Keypair) {
stamp := model.SignedStampedDoc{
Timestamp: utilities.Now(),
PublicKey: key.PubID(),
}
obj.Stamp(stamp)
signature, err := key.Sign(obj.PartToSign())
if err != nil {
log.Errorf("signing %s, %s ", obj, err)
}
stamp.Signature = string(signature)
obj.Stamp(stamp)
}
func MakeApprovedClaim(c model.Claim, key crypto.Keypair) model.ApprovedClaim {
res := model.ApprovedClaim{
Claim: c,
}
StampAndSign(&res, key)
return res
}
func MakeVote(perm string, object string, value string, key crypto.Keypair) model.OpenBallot {
var res = model.OpenBallot{
Permission: perm,
VoteObject: object,
VoteValue: value,
}
StampAndSign(&res, key)
return res
}
func MakeTransaction(to string, amount int, key crypto.Keypair) model.Transaction {
tx := model.Transaction{
To: to,
Amount: uint64(amount),
}
StampAndSign(&tx, key)
return tx
}
package chain
import (
"gitlab.com/bertrandbenj/politikojj/crypto"
"gitlab.com/bertrandbenj/politikojj/model"
)
var InitialRoles = map[string][]string{
model.Citizen: {
model.VoteGrantClaim,
model.VoteRevokeClaim,
model.VoteGrantPermissionToRole,
model.VoteRevokePermissionToRole,
model.VoteVariableValue,
model.VoteAmendment,
},
model.CitizenshipOfficer: {
"GrantCitizenship",
"RevokeCitizenship"}}
var InitialRolesInheritance = map[string][]string{
"CitizenshipOfficer": {model.Citizen},
}
func (cs BlockChainService) ClaimCitizenship(name string, key crypto.Keypair) (model.Claim, string) {
c := MakeClaim(name, model.Citizen, key)
cid, err := cs.Store.PutObject(c)
if err != nil {
log.Info("Error Claiming citizenship in PutObject ", err)
}
return c, cid
}
func (cs BlockChainService) ApproveClaim(c model.Claim, key crypto.Keypair) (approvedClaim model.ApprovedClaim, cid string) {
//c := Pools.Claims[cCid]
approved := MakeApprovedClaim(c, key)
cid, err := cs.Store.PutObject(approved)
if err != nil {
log.Info("Error approving claim in PutObject ", err)
return
}
//delete(Pools.Claims, cCid)
return approved, cid
}
func (cs BlockChainService) AddCitizen(d *model.Demos, key crypto.Keypair, name string) {
pid := key.PubID()
c, _ := cs.ClaimCitizenship(name, key)
_, accId := cs.ApproveClaim(c, key)
d.CitizensByName[c.ChosenName] = model.CitizenOfName{
PublicKey: []model.CidLink{{Link: pid}},
CitizenshipClaim: model.CidLink{Link: accId}}
d.PublicKeysByRole["Citizen"] = []model.CidLink{{Link: pid}}
}
func (cs BlockChainService) Genesis(key crypto.Keypair, constitutionFile string) model.Block {
// build Demos with one single user being the nodes config
demos := model.Demos{
Population: 1,
CitizensByName: make(map[string]model.CitizenOfName),
PublicKeysByRole: make(map[string][]model.CidLink),
}
cs.AddCitizen(&demos, key, "Cleisthenes")
demosCid, err := cs.Store.PutObject(demos)
if err != nil {
log.Info("error inserting Demos PutObject ", err)
}
// build Cratos with basic roles and permission
cratos := model.Cratos{
Roles: model.Roles{
Permission: InitialRoles,
Inheritance: InitialRolesInheritance},
//Votes: make(map[string]model.OpenBallot),
Claims: model.Claims{},
ProposePredicate: []string{
model.ProposeCreateAccount,
model.ProposeNewVariable,
model.ProposeTaxRevenueTarget,
model.ProposeTaxType,
model.ProposeAccountPermission,
model.ProposeNewRole,
model.ProposeAddPermission,
model.ProposeTax,
model.ProposeTaxVar,
},
Variables: make(map[string]model.Vars),
//VarVotesByNameByPk: make(map[string]map[string]interface{}),
Taxes: make(map[string]model.Tax),
PublicAccounts: make(map[string]model.PublicAccount)}
cratos.Variables["Inflation"] = model.Vars{
Type: model.UnitIntervalType,
Value: 0.08,
VotesByPk: make(map[string]interface{}),
}
//node.DeserializeFromCratos(cratos)
//log.Info("test rbac ", node.IsAuthorized("Citizen", "VoteTransactionTax"))
cratosCid, err := cs.Store.PutObject(cratos)
if err != nil {
log.Info("error inserting Cratos PutObject ", err)
}
// build a default currency
ccy := model.Currency{
Name: "CDT",
MonetaryMass: 0,
UnitBase: 0,
Dividend: uint64(100000),
Wallets: make(map[string]uint64),
JointWallets: make(map[string]uint64),
PublicWallets: make(map[string]uint64),
}
ccy.Wallets[key.PubID()] = ccy.Dividend
ccy.MonetaryMass = ccy.Dividend
ccyCid, err := cs.Store.PutObject(ccy)
if err != nil {
log.Info("error inserting Curency PutObject ", err)
}
// publish constitution or use given cid and set it as genesis's previous block
var constCid string
if len(constCid) == 0 {
constCid = cs.Store.StoreFile(constitutionFile)
log.Infof("constitution read local %s, stored as %s", constitutionFile, constCid)
}
genesisBlock := model.Block{
//SignedStampedDoc: model.SignedStampedDoc{
// Timestamp: utilities.Now(),
// PublicKey: key.PubID(),
//},
Previous: model.CidLink{Link: constCid},
Network: "TestNet",
Demos: model.CidLink{Link: demosCid},
Cratos: model.CidLink{Link: cratosCid},
Currency: model.CidLink{Link: ccyCid},
Difficulty: model.PersonalizedAdaptivePoW{
CommonDifficulty: 70,
PreviousBlockTimes: [12]int64{},
PersonalHandicap: make(map[string]uint8)},
Evidences: []model.Evidence{},
Nonce: "0x010000",
}
StampAndSign(&genesisBlock, key)
//cs.SignBlock(&genesisBlock, key.PrivateKey)
return genesisBlock
}
package chain
import (
"encoding/hex"
"gitlab.com/bertrandbenj/politikojj/crypto"
"gitlab.com/bertrandbenj/politikojj/model"
)
func (cs BlockChainService) IsBlockValid(newBlock model.Block) bool {
if cs.network != newBlock.Network {
return false
}
pubkey := crypto.ParsePublicKey(newBlock.PublicKey)
sign, _ := hex.DecodeString(newBlock.GetSignature())
if ok, err := pubkey.Verify(newBlock.PartToSign(), sign); !ok {
log.Warning("Block Signature invalid", err)
return false
}
if !newBlock.IsHashValid(newBlock.Difficulty) {
log.Warning("Block Hash invalid", newBlock.PoWHash)
return false
}
return true
}
func (cs BlockChainService) IsBlockDeltaValid(new, old model.Block) bool {
if new.Index != old.Index+1 {
//log.Error("block invalid... index increment not respected")
return false
}
return true
}
package crypto
import (
"encoding/hex"
"github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/op/go-logging"
"io/ioutil"
"os"
"path/filepath"
)
type PublicK crypto.PubKey
type PrivateK crypto.PrivKey
var log = logging.MustGetLogger("crypto")
type Keypair struct {
PrivateKey PrivateK
PublicKey PublicK
}
type SerializedKey struct {
Pub string
Priv string
Pubcid string
}
func (k *Keypair) Sign(b []byte) (string, error) {
res, err := k.PrivateKey.Sign(b)
return hex.EncodeToString(res), err
}
func (k *Keypair) Save(filename string) (ok bool) {
if k.PrivateKey == nil {
log.Error("No private key ")
return false
}
b, err := crypto.MarshalPrivateKey(k.PrivateKey)
if err != nil {
log.Error("problem marshalling key ", k.PubID())
return false
}
dir := filepath.Dir(filename)
_ = os.MkdirAll(dir, os.ModePerm)
err = ioutil.WriteFile(filename, b, 0777)
if err != nil {
log.Error("problem writing key ", k.PubID())
return false
}
return true
}
func ParsePublicKey(str string) PublicK {
id, err := peer.Decode(str)
if err != nil {
log.Error("problem Decoding key", str, err)
return nil
}
k, err := id.ExtractPublicKey()
if err != nil {
log.Error("problem extracting key", str, err)
return nil
}
return k
}
func (k *Keypair) PubID() string {
id, err := peer.IDFromPublicKey(k.PublicKey)
if err != nil {
log.Error("Could not get Pubkey ID")
}
return peer.Encode(id)
}
func LoadKey(filename string) Keypair {
b, err := ioutil.ReadFile(filename)
if err != nil {
log.Error("problem reading key ", filename)
return Keypair{}
}
pri, err := crypto.UnmarshalPrivateKey(b)
if err != nil {
log.Error("problem unmarshalling key ", filename)
return Keypair{}
}
return Keypair{
PrivateKey: pri,
PublicKey: pri.GetPublic(),
}
}
func (k *Keypair) Serialize() SerializedKey {
pub, _ := k.PublicKey.Bytes()
priv, _ := k.PrivateKey.Bytes()
return SerializedKey{
Pub: string(pub),
Priv: string(priv),
Pubcid: k.PubID(),
}
}
func GenEd25519() Keypair {
return Gen(crypto.Ed25519, 0)
}
func Gen(typ int, bits int) Keypair {
priv, pub, err := crypto.GenerateKeyPair(typ, bits)
if err != nil {
log.Error("Could not Generate Keypair ")
}
return Keypair{
PrivateKey: priv,
PublicKey: pub,
}
}
package model
import (
"encoding/hex"
"fmt"
)
// ==== Signature related stuff ====
type SignedStampedDoc struct {
Timestamp int64
PublicKey string
Signature string
}
func (d *SignedStampedDoc) Stamp(s SignedStampedDoc) {
d.PublicKey = s.PublicKey
d.Timestamp = s.Timestamp
d.Signature = s.Signature
}
func (d SignedStampedDoc) PartToSign() []byte {
return []byte(fmt.Sprintf("%d", d.Timestamp) + d.PublicKey)
}
func (d SignedStampedDoc) Details() (pk string, sign []byte) {
s, _ := hex.DecodeString(d.Signature)
return d.PublicKey, s
}
func (d SignedStampedDoc) GetSignature() string {
return d.Signature
}
func (d SignedStampedDoc) GetPublicKey() string {
return d.PublicKey
}
type Signable interface {
PartToSign() []byte
Details() (pk string, sign []byte)
GetSignature() string
GetPublicKey() string
Stamp(SignedStampedDoc)
}
// Observer pattern
type Observable interface {
Register(observer Observer)
Deregister(observer Observer)
NotifyAll()
}
type Observer interface {
Update(string)
GetID() string
}
type ObservableList struct {
observerList []Observer
}
func (c *ObservableList) Register(o Observer) {
c.observerList = append(c.observerList, o)
}
func (c *ObservableList) Deregister(o Observer) {
c.observerList = removeFromslice(c.observerList, o)
}
func (c *ObservableList) NotifyAll() {
for _, observer := range c.observerList {
observer.Update("new")
}
}
func removeFromslice(observerList []Observer, observerToRemove Observer) []Observer {
observerListLength := len(observerList)
for i, observer := range observerList {
if observerToRemove.GetID() == observer.GetID() {
observerList[observerListLength-1], observerList[i] = observerList[i], observerList[observerListLength-1]
return observerList[:observerListLength-1]
}
}
return observerList
}
// ==== Other stuff ====
type CidLink struct {
Link string `json:"/,omitempty"`
}
type Evidence struct {
CidLink
Object interface{}
}
package model
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"math"
"strings"
)
const (
DtDiffEval = 12
AvgGenTime = 300
)
var MinGenTime = math.Floor(AvgGenTime / 1.189)
var MaxGenTime = math.Ceil(AvgGenTime * 1.189)
var MaxSpeed = 1 / MinGenTime
var MinSpeed = 1 / MaxGenTime
// the hash is not part of the block itself as it is the ipfs cid
type Block struct {
// header
Index uint32
Previous CidLink `json:",omitempty"`
Network string
// data
Demos CidLink
Cratos CidLink
Currency CidLink
Evidences []Evidence
// validation
SignedStampedDoc
Difficulty PersonalizedAdaptivePoW
Nonce string
PoWHash string
}
func (block Block) IsHashValid(difficulty PersonalizedAdaptivePoW) bool {
zeros, rem := difficulty.GetNbZerosAndRemainder()
prefix := strings.Repeat("0", zeros)
if !strings.HasPrefix(block.PoWHash, prefix) {
return false
}
remain := strings.TrimPrefix(block.PoWHash, prefix)[0:1]
res := false
switch rem {
case 0:
res = strings.ContainsAny(remain, "0123456789ABCDEF")
break
case 1:
res = strings.ContainsAny(remain, "0123456789ABCDE")
break
case 2:
res = strings.ContainsAny(remain, "0123456789ABCD")
break
case 3:
res = strings.ContainsAny(remain, "0123456789ABC")
break
case 4:
res = strings.ContainsAny(remain, "0123456789AB")
break
case 5:
res = strings.ContainsAny(remain, "0123456789A")
break
case 6:
res = strings.ContainsAny(remain, "0123456789")
break
case 7:
res = strings.ContainsAny(remain, "012345678")
break
case 8:
res = strings.ContainsAny(remain, "01234567")
break
case 9:
res = strings.ContainsAny(remain, "0123456")
break
case 10:
res = strings.ContainsAny(remain, "012345")
break
case 11:
res = strings.ContainsAny(remain, "01234")
break
case 12:
res = strings.ContainsAny(remain, "0123")
break
case 13:
res = strings.ContainsAny(remain, "012")
break
case 14:
res = strings.ContainsAny(remain, "01")
break
case 15:
res = strings.ContainsAny(remain, "0")
break
}
return res
}
func (block *Block) HashPow() {
record := block.PartToHash()
h := sha256.New()
h.Write([]byte(record))
hashed := h.Sum(nil)
(*block).PoWHash = hex.EncodeToString(hashed)
}
func (block Block) Redacted() interface{} {
res, _ := json.MarshalIndent(block, "", " ")
return string(res)
}
func (block Block) PartToSign() []byte {
evidences := ""
for _, v := range block.Evidences {
evidences += v.Link
}
buf := bytes.NewBufferString(fmt.Sprint(block.Index) +
fmt.Sprint(block.Timestamp) +
block.Previous.Link +
block.Demos.Link +
block.Cratos.Link +
block.Currency.Link +
evidences +
block.PublicKey)
buf.Write(block.Difficulty.PartToSign())
return buf.Bytes()
}
func (block Block) PartToHash() string {
return block.Signature + block.Nonce
}
package model
import (
"bytes"
"time"
)
const ClaimExpiry = 1
type Claim struct {
ChosenName string
Role string
SignedStampedDoc
}
type ApprovedClaim struct {
Claim Claim
SignedStampedDoc
}
type Expirable interface {
Expired() bool
}
func (c Claim) Expired() bool {
return time.Unix(c.Timestamp, 0).Add(time.Hour * 24 * ClaimExpiry).Before(time.Now().UTC())
}
func (c Claim) PartToSign() []byte {
buf := bytes.NewBuffer(c.SignedStampedDoc.PartToSign())
buf.WriteString(c.ChosenName)
buf.WriteString(c.Role)
return buf.Bytes()
}
func (approved ApprovedClaim) PartToSign() []byte {
buf := bytes.NewBuffer(approved.SignedStampedDoc.PartToSign())
buf.Write(approved.Claim.PartToSign())
return buf.Bytes()
}
package model
import "strings"
// Constitutional Proposition predicates
const (
ProposeTax = "ProposeTax"
ProposeTaxVar = "ProposeTaxVar"
ProposeTaxRevenueTarget = "ProposeTaxRevenueTarget"
ProposeTaxType = "ProposeTaxType"
ProposeNewVariable = "ProposeNewVariable"
ProposeCreateAccount = "ProposeCreateAccount"
ProposeAccountPermission = "ProposeAccountPermission"
ProposeNewRole = "ProposeNewRole"
ProposeAddPermission = "ProposeAddPermission"
)
var AvailablePropose = []string{
ProposeTax,
ProposeTaxVar,
ProposeTaxRevenueTarget,
ProposeTaxType,
ProposeNewVariable,
ProposeCreateAccount,
ProposeAccountPermission,
ProposeNewRole,
ProposeAddPermission}
// Tax types
const (
GlobalTransactionTax = "GlobalTransactionTax"
BytesTax = "BytesTax"
)
var AvailableTaxType = []string{GlobalTransactionTax, BytesTax}
// Variable types
const (
UnitIntervalType = "UnitIntervalValue"
CategoryType = "CategoryVariable"
IntegerType = "IntegerVariable"
)
var AvailableVariableTax = []string{UnitIntervalType, CategoryType, IntegerType}
// Installation procedures
var InstallAmendment = map[string]func(predicate *Rule, cratos *Cratos){
ProposeTax: installTax,
ProposeTaxVar: installTaxVar,
ProposeTaxRevenueTarget: installTaxRevenueTarget,
ProposeTaxType: installTaxType,
ProposeNewVariable: installNewVariable,
ProposeCreateAccount: installNewAccount,
ProposeAccountPermission: installAccountPermission,
ProposeNewRole: installNewRole,
ProposeAddPermission: installAddPermission,
}
func installAddPermission(predicate *Rule, cratos *Cratos) {
perms := cratos.Roles.Permission[predicate.Subject]
cratos.Roles.Permission[predicate.Subject] = append(perms, predicate.Object)
}
func installNewRole(predicate *Rule, cratos *Cratos) {
cratos.Roles.Permission[predicate.Object] = []string{}
if !strings.HasPrefix(predicate.Subject, ".") {
cratos.Roles.Inheritance[predicate.Object] = []string{predicate.Subject}
}
}
func installAccountPermission(predicate *Rule, cratos *Cratos) {
acc := cratos.PublicAccounts[predicate.Subject]
acc.Permission = predicate.Object
cratos.PublicAccounts[predicate.Subject] = acc
}
func installNewAccount(predicate *Rule, cratos *Cratos) {
cratos.PublicAccounts[predicate.Subject] = PublicAccount{Name: predicate.Subject}
}
func installNewVariable(predicate *Rule, cratos *Cratos) {
res := Vars{
Type: predicate.Object,
Value: nil,
VotesByPk: make(map[string]interface{}),
}
if predicate.Object == UnitIntervalType {
res.Value = 0.5
}
cratos.Variables[predicate.Subject] = res
//cratos.VarVotesByNameByPk[predicate.Subject] = make(map[string]interface{})
}
func installTaxRevenueTarget(predicate *Rule, cratos *Cratos) {
tax := cratos.Taxes[predicate.Subject]
tax.Account = predicate.Object
cratos.Taxes[predicate.Subject] = tax
}
func installTaxType(predicate *Rule, cratos *Cratos) {
tax := cratos.Taxes[predicate.Subject]
tax.Type = 0
cratos.Taxes[predicate.Subject] = tax
}
func installTax(predicate *Rule, cratos *Cratos) {
cratos.Taxes[predicate.Object] = Tax{
Name: predicate.Object,
}
}
func installTaxVar(predicate *Rule, cratos *Cratos) {
t := cratos.Taxes[predicate.Subject]
t.Variable = predicate.Object
cratos.Taxes[predicate.Subject] = t
}
// Basic predicate integrity check
var VerifyPredicate = map[string]func(predicate *Rule, cratos *Cratos) bool{
ProposeTax: func(p *Rule, cratos *Cratos) bool { return true },
ProposeTaxVar: func(p *Rule, cratos *Cratos) bool { return true },
ProposeTaxRevenueTarget: func(p *Rule, cratos *Cratos) bool { return true },
ProposeTaxType: checkTaxType,
ProposeNewVariable: checkNewVar,
ProposeCreateAccount: func(p *Rule, cratos *Cratos) bool { return true },
ProposeAccountPermission: func(p *Rule, cratos *Cratos) bool { return true },
ProposeNewRole: func(p *Rule, cratos *Cratos) bool { return true },
ProposeAddPermission: func(p *Rule, cratos *Cratos) bool { return true },
}
func checkNewVar(predicate *Rule, _ *Cratos) bool {
switch predicate.Object {
case UnitIntervalType:
return true
case CategoryType:
return true
case IntegerType:
return true
}
return false
}
func checkTaxType(predicate *Rule, _ *Cratos) bool {
switch predicate.Object {
case GlobalTransactionTax:
return true
case BytesTax:
return true
}
return false
}
package model
import (
"bytes"
"errors"
"github.com/op/go-logging"
"reflect"
"strconv"
)
var log = logging.MustGetLogger("chain")
type OpenBallot struct {
Permission string
VoteObject string
VoteValue string
SignedStampedDoc
}
func (b OpenBallot) PartToSign() []byte {
buf := bytes.NewBuffer(b.SignedStampedDoc.PartToSign())
buf.WriteString(b.Permission)
buf.WriteString(b.VoteObject)
buf.WriteString(b.VoteValue)
return buf.Bytes()
}
type Rule struct {
Subject string
Predicate string
Object string
}
type Amendment struct {
Name string
Predicates []Rule
SignedStampedDoc
}
func (a Amendment) PartToSign() []byte {
buf := bytes.NewBuffer(a.SignedStampedDoc.PartToSign())
buf.WriteString(a.Name)
return buf.Bytes()
}
//func (a Amendment) Details() (string, string) {
// return a.Name, a.Name
//}
//func (a Amendment) VoteType() int {
// return VoteAmendment
//}
type Roles struct {
Permission map[string][]string
Inheritance map[string][]string
}
type UnitIntervalVariable float64
type CategoryVariable string
type IntegerVariable int64
//type Votable interface {
// VoteType() int
//}
type VoteableVariable interface {
IsLegal(s string) bool
//Parse(s string, res interface{})
Description() string
FloatValue() float64
}
type Value interface{}
type Ballots struct {
Vote []OpenBallot
}
type Claims struct {
Claim []ApprovedClaim
}
type Tax struct {
Name string
Variable string
Account string
Type uint8
}
type Vars struct {
//Name string
Type string
Value interface{}
VotesByPk map[string]interface{}
}
func (variable *Vars) Avg() interface{} {
var avgF = float64(0)
for _, v := range variable.VotesByPk {
switch v.(type) {
case float64:
avgF += v.(float64)
break
default:
log.Info("Unhandled Type", reflect.TypeOf(v))
}
}
avgF /= float64(len(variable.VotesByPk))
(*variable).Value = avgF
return avgF
}
type Cratos struct {
Roles Roles
//Votes map[string]OpenBallot
Claims Claims
Variables map[string]Vars
ProposePredicate []string
//VarVotesByNameByPk map[string]map[string]interface{}
Taxes map[string]Tax
PublicAccounts map[string]PublicAccount
}
func (v UnitIntervalVariable) IsLegal(s string) bool {
if res, err := strconv.ParseFloat(s, 32); err == nil {
if res >= 0 && res <= 1 {
return true
}
}
return false
}
func ParseUnitIntervalVar(s string) UnitIntervalVariable {
if res, err := strconv.ParseFloat(s, 64); err == nil {
if res >= 0 && res <= 1 {
return UnitIntervalVariable(res)
}
}
return 0
}
func ParseIntegerVar(s string) IntegerVariable {
if res, err := strconv.ParseInt(s, 0, 64); err == nil {
return IntegerVariable(res)
}
return IntegerVariable(0)
}
func ParseCategoryVar(s string) CategoryVariable {
return CategoryVariable(s)
}
func (v UnitIntervalVariable) Description() string {
return "a floating point number in the interval [0,1]"
}
func (v UnitIntervalVariable) FloatValue() float64 {
return float64(v)
}
func (v CategoryVariable) IsLegal(s string) bool {
return true
}
func (v CategoryVariable) Description() string {
return "A String representation of the Category"
}
func (v CategoryVariable) FloatValue() float64 {
err := errors.New("CategoryVariable cannot return a float value")
log.Error(err)
return 0.0
}
func (v IntegerVariable) IsLegal(s string) bool {
_, err := strconv.Atoi(s)
return err == nil
}
func (v IntegerVariable) Description() string {
return "an integer value point number in the interval [0,1]"
}
func (v IntegerVariable) FloatValue() float64 {
return float64(v)
}
package model
import (
"bytes"
"fmt"
)
type Currency struct {
Name string
MonetaryMass uint64
UnitBase uint8
//Inflation float32
CitizenCount uint32
Dividend uint64
Wallets map[string]uint64
JointWallets map[string]uint64
PublicWallets map[string]uint64
}
type Wallet struct {
PublicKey string
Balance uint64
}
type JointWallet struct {
PublicKeys []string
Balance uint64
}
type PublicWallet struct {
Name string
Balance uint64
}
type PublicAccount struct {
Name string
Permission string
}
type Transaction struct {
SignedStampedDoc
To string
Amount uint64
}
func (t Transaction) PartToSign() []byte {
buf := bytes.NewBuffer(t.SignedStampedDoc.PartToSign())
buf.WriteString(t.To)
buf.WriteString(fmt.Sprintf("%d", t.Amount))
return buf.Bytes()
}
func (ccy Currency) SumAccounts() uint64 {
sum := uint64(0)
for _, v := range ccy.Wallets {
sum += v
}
for _, v := range ccy.JointWallets {
sum += v
}
for _, v := range ccy.PublicWallets {
sum += v
}
return sum
}
package model
import (
"bytes"
"fmt"
)
type PersonalizedAdaptivePoW struct {
CommonDifficulty uint8
PreviousBlockTimes [DtDiffEval]int64
PersonalHandicap map[string]uint8
}
func (pow PersonalizedAdaptivePoW) PartToSign() []byte {
buf := bytes.NewBufferString(fmt.Sprintf("%d", pow.CommonDifficulty))
for _, v := range pow.PreviousBlockTimes {
buf.WriteString(fmt.Sprintf("%d", v))
}
return buf.Bytes()
}
func (pow PersonalizedAdaptivePoW) GetNbZerosAndRemainder() (int, int) {
nbZeros := int(pow.CommonDifficulty) / 16
rem := int(pow.CommonDifficulty) - 16*nbZeros
return nbZeros, rem
}
package network
import (
"encoding/json"
"github.com/gorilla/mux"
"gitlab.com/bertrandbenj/politikojj/model"
"io"
"net/http"
"strconv"
)
func (rest RestService) PostBlock(w http.ResponseWriter, r *http.Request) {
var newBlock model.Block
if rest.DecodeReceivedJSON(w, r, &newBlock) {
rest.smith.CheckForkOrChain(newBlock)
rest.respondObjectAsJson(w, rest.chain.HeadBlock())
}
}
func (rest RestService) GetCratos(w http.ResponseWriter, _ *http.Request) {
x := model.Wrapper{
CID: rest.chain.GetHead().Block.Cratos.Link,
Data: rest.chain.GetHead().Cratos,
}
rest.respondObjectAsJson(w, x)
}
func (rest RestService) GetCCY(w http.ResponseWriter, _ *http.Request) {
x := model.Wrapper{
CID: rest.chain.GetHead().Block.Currency.Link,
Data: rest.chain.GetHead().Currency,
}
rest.respondObjectAsJson(w, x)
}
func (rest RestService) GetDemos(w http.ResponseWriter, _ *http.Request) {
x := model.Wrapper{
CID: rest.chain.GetHead().Block.Demos.Link,
Data: rest.chain.GetHead().Demos,
}
rest.respondObjectAsJson(w, x)
}
func (rest RestService) GetHeadCid(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, rest.chain.GetHead().Cid)
}
func (rest RestService) GetBlock(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bid, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
rest.respondObjectAsJson(w, rest.chain.ByIndex(uint32(bid)))
}
func (rest RestService) GetBlockchain(w http.ResponseWriter, _ *http.Request) {
var prettyChain []model.Wrapper
rest.chain.BackwardWalk(func(block model.Block, cid string) {
prettyChain = append(prettyChain, model.Wrapper{
CID: cid,
Data: block})
})
bytes, err := json.MarshalIndent(prettyChain, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(bytes)
}
package network
import (
"gitlab.com/bertrandbenj/politikojj/model"
"net/http"
)
func (rest RestService) GetClaimRole(w http.ResponseWriter, _ *http.Request) {
rest.respondObjectAsJson(w, rest.smith.Pools.Claims)
}
func (rest RestService) GetApprovedClaimRole(w http.ResponseWriter, _ *http.Request) {
rest.respondObjectAsJson(w, rest.smith.Pools.ApprovedClaims)
}
func (rest RestService) PostClaimRole(w http.ResponseWriter, r *http.Request) {
var cl model.Claim
log.Info("entering ... ")
if rest.DecodeReceivedJSON(w, r, &cl) {
if rest.VerifySignable(w, &cl) {
if cid, ok := rest.StoreToIPFS(w, cl); ok {
rest.smith.Pools.Add(cid, cl)
log.Warning(rest.smith.Pools.Claims)
rest.respondObjectAsJson(w, rest.smith.Pools.Claims)
}
}
}
}
func (rest RestService) PostApprovedClaim(w http.ResponseWriter, r *http.Request) {
var acl model.ApprovedClaim
if rest.DecodeReceivedJSON(w, r, &acl) {
if rest.VerifySignable(w, &acl) {
if cid, ok := rest.StoreToIPFS(w, acl); ok {
rest.smith.Pools.Add(cid, acl)
rest.respondObjectAsJson(w, rest.smith.Pools.ApprovedClaims)
}
}
}
}
package network
import (
"gitlab.com/bertrandbenj/politikojj/model"
"net/http"
)
func (rest RestService) PostVote(w http.ResponseWriter, r *http.Request) {
var ballot = model.OpenBallot{}
if rest.DecodeReceivedJSON(w, r, &ballot) {
log.Info("Decoded ballot ", ballot)
if cid, ok := rest.StoreToIPFS(w, ballot); ok {
rest.smith.Pools.Add(cid, ballot)
}
}
}
func (rest RestService) PostAmendment(w http.ResponseWriter, r *http.Request) {
var amendment model.Amendment
if rest.DecodeReceivedJSON(w, r, &amendment) {
if rest.VerifySignable(w, &amendment) {
log.Warning("amendment", amendment)
if rest.VerifyRuleDataType(amendment) {
if cid, ok := rest.StoreToIPFS(w, amendment); ok {
rest.smith.Pools.Add(cid, amendment)
rest.respondObjectAsJson(w, model.Wrapper{
CID: cid,
Data: amendment,
})
}
}
}
}
}
// check some of the data type
func (rest RestService) VerifyRuleDataType(a model.Amendment) bool {
for _, v := range a.Predicates {
cratos := rest.chain.GetHead().Cratos
if !model.VerifyPredicate[v.Predicate](&v, &cratos) {
return false
}
}
return true
}
func (rest RestService) GetRBAC(w http.ResponseWriter, _ *http.Request) {
rest.respondObjectAsJson(w, rest.smith.CratosP.SerializeForCratos())
}
package network
import (
"encoding/json"
"net/http"
)
var Endpoints endpoints
type endpoints struct {
Rest []string
}
func (rest RestService) GetNetwork(w http.ResponseWriter, _ *http.Request) {
log.Info("GetNetwork", Endpoints)
bytes, _ := json.MarshalIndent(Endpoints, "", "\t")
w.Header().Set("Content-Type", "application/json")
n, err := w.Write(bytes)
if err != nil {
log.Error("writing ", n, "bytes of data ", err)
}
}
package network
import (
"bufio"
"encoding/json"
"github.com/libp2p/go-libp2p-core/network"
"github.com/mitchellh/mapstructure"
"gitlab.com/bertrandbenj/politikojj/model"
"sync"
)
func (p2p ServerP2P) readBlock(rw *bufio.ReadWriter, stream network.Stream) {
remote := stream.Conn().RemotePeer().Pretty()
for {
log.Noticef("%d Read Block from %s", p2p.port, remote)
str, err := rw.ReadString('\n')
if err != nil {
log.Error("ReadString", err)
}
if str == "" {
log.Error("Returning", err)
return
}
if str != "\n" {
newBlock := model.Block{}
if err := json.Unmarshal([]byte(str), &newBlock); err != nil {
log.Error("json.Unmarshal", err)
}
err = p2p.smith.CheckForkOrChain(newBlock)
if err != nil {
log.Errorf("%d %s", p2p.port, err)
} else {
log.Noticef("%d Chained block received from %s", p2p.port, remote)
}
}
}
}
func (p2p *ServerP2P) readPoolDoc(rw *bufio.ReadWriter, stream network.Stream) {
remote := stream.Conn().RemotePeer().Pretty()
for {
log.Noticef("%d Read Doc from %s", p2p.port, remote)
str, err := rw.ReadString('\n')
if err != nil {
log.Error("Reading delimiter", err)
}
if str == "" {
log.Error("Returning", err)
return
}
if str != "\n" {
var doc model.Wrapper
if err := json.Unmarshal([]byte(str), &doc); err != nil {
log.Error("json.Unmarshal", err)
} else {
log.Notice("Received new document", doc.CID, doc)
var ssd model.SignedStampedDoc
_ = mapstructure.Decode(doc.Data, &ssd)
switch doc.Type {
case "OpenBallot":
var result model.OpenBallot
_ = mapstructure.Decode(doc.Data, &result)
result.Stamp(ssd)
p2p.smith.Pools.Add(doc.CID, result)
case "Claim":
var result model.Claim
_ = mapstructure.Decode(doc.Data, &result)
result.Stamp(ssd)
p2p.smith.Pools.Add(doc.CID, result)
case "ApprovedClaim":
var result model.ApprovedClaim
_ = mapstructure.Decode(doc.Data, &result)
result.Stamp(ssd)
p2p.smith.Pools.Add(doc.CID, result)
case "Amendment":
var result model.Amendment
_ = mapstructure.Decode(doc.Data, &result)
result.Stamp(ssd)
p2p.smith.Pools.Add(doc.CID, result)
case "Transaction":
var result model.Transaction
_ = mapstructure.Decode(doc.Data, &result)
result.Stamp(ssd)
p2p.smith.Pools.Add(doc.CID, result)
}
}
}
}
}
func (p2p ServerP2P) broadcastBlock(remoteHead model.Block) {
var writeMutex = &sync.Mutex{}
p2p.streamLock.RLock()
for remote, rw := range p2p.BlockStreams {
writeMutex.Lock()
log.Infof("%d sending head %d to %s", p2p.port, remoteHead.Index, remote)
if rw == nil {
log.Errorf("%d broadcasting head %d to %s", p2p.port, remoteHead.Index, remote)
return
}
bytes, err := json.Marshal(remoteHead)
if err != nil {
log.Error("Marshal", err)
}
_, err = rw.Write(bytes)
if err != nil {
log.Error("Write block data", err)
}
_, err = rw.WriteString("\n")
if err != nil {
log.Error("Writing delimiter", err)
}
_ = rw.Flush()
writeMutex.Unlock()
}
p2p.streamLock.RUnlock()
}
func (p2p *ServerP2P) broadcastDoc(doc model.Wrapper) {
var writeMutex = &sync.Mutex{}
p2p.streamLock.RLock()
for remote, rw := range p2p.DocsStreams {
go func(remote string, writer *bufio.ReadWriter) {
log.Infof("%d sending Doc %s to %s", p2p.port, doc.Type, remote)
bytes, err := json.Marshal(doc)
if err != nil {
log.Error("Unmarshalling", err)
return
}
writeMutex.Lock()
n, err := writer.Write(bytes)
if err != nil {
log.Error("Write doc data", err, n, len(bytes))
return
}
_, err = writer.WriteString("\n")
if err != nil {
log.Error("Writing delimiter", err)
return
}
err = writer.Flush()
if err != nil {
log.Error("Flushing rw", err)
return
}
writeMutex.Unlock()
}(remote, rw)
}
p2p.streamLock.RUnlock()
}
package network
import (
"bufio"
"context"
"fmt"
"github.com/libp2p/go-libp2p-core/crypto"
"gitlab.com/bertrandbenj/politikojj/model"
mrand "math/rand"
"strings"
"time"
"gitlab.com/bertrandbenj/politikojj/chain"
"gitlab.com/bertrandbenj/politikojj/node"
"gitlab.com/bertrandbenj/politikojj/storage"
"sync"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/multiformats/go-multiaddr"
)
type ServerP2P struct {
chain *chain.BlockChainService
store storage.Store
smith *node.BlockSmith
port int
multiAddress multiaddr.Multiaddr
secio bool
seed int64
host host.Host
Sleep time.Duration
peerChan chan peer.AddrInfo
BlockStreams map[string]*bufio.ReadWriter
blockChan chan model.Block
DocsStreams map[string]*bufio.ReadWriter
docsChan chan model.Wrapper
streamLock *sync.RWMutex
}
func (p2p *ServerP2P) Update(s string) {
log.Infof("%d Promoting head update... broadcasting %s", p2p.port, s)
//p2p.updatedHead.Set()
p2p.blockChan <- p2p.chain.GetHead().Block
}
func (p2p *ServerP2P) GetID() string {
return p2p.multiAddress.String()
}
func NewP2P(ctx context.Context, smith *node.BlockSmith, listenF int, seed int64, secio bool, bootstrap []multiaddr.Multiaddr) *ServerP2P {
P2P := ServerP2P{
chain: smith.Chain,
store: smith.Chain.Store,
smith: smith,
port: listenF,
secio: secio,
seed: seed,
Sleep: 5 * time.Second,
blockChan: make(chan model.Block, 1),
BlockStreams: make(map[string]*bufio.ReadWriter),
streamLock: &sync.RWMutex{},
DocsStreams: make(map[string]*bufio.ReadWriter),
docsChan: make(chan model.Wrapper, 1),
}
smith.Chain.Head.Register(&P2P)
smith.Pools.Attach(P2P.docsChan)
log.Infof("Init P2P Server using seed %d, secure I/O ? %t ", seed, secio)
if listenF == 0 {
log.Fatal("Please provide a port to bind on with -p2p_port")
}
// Make a host that listens on the given multi-address
P2P.makeBasicHost(ctx, bootstrap)
return &P2P
}
func (p2p ServerP2P) handlePoolStream(s network.Stream) {
remote := s.Conn().RemotePeer().Pretty()
log.Infof("%d handle a pool stream! %s %s", p2p.port, s.Protocol(), remote)
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
p2p.streamLock.Lock()
p2p.DocsStreams[remote] = rw
p2p.streamLock.Unlock()
go p2p.readPoolDoc(rw, s)
}
func (p2p ServerP2P) handleStream(s network.Stream) {
p2p.handleBlockStream(s, "router")
}
func (p2p ServerP2P) handleBlockStream(s network.Stream, origin string) {
remote := s.Conn().RemotePeer().Pretty()
log.Infof("%d handle a new stream! %s %s %s", p2p.port, s.Protocol(), remote, origin)
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
p2p.streamLock.Lock()
p2p.BlockStreams[remote] = rw
p2p.streamLock.Unlock()
go p2p.readBlock(rw, s)
//go p2p.writeHead(rw, s)s
}
// Listen to new head block and broadcast to connected peers
func (p2p ServerP2P) runBroadcaster() {
for {
select {
case newDoc := <-p2p.docsChan:
log.Infof("%d about to broadcast Doc %s ", p2p.port, newDoc.CID)
go p2p.broadcastDoc(newDoc)
break
case newHead := <-p2p.blockChan:
log.Infof("%d about to broadcast Block n°%d ", p2p.port, newHead.Index)
go p2p.broadcastBlock(newHead)
break
}
}
}
// Creates a LibP2P host with a random peer ID listening on the
// given multi-address. It will use secio if secio is true.
func (p2p *ServerP2P) makeBasicHost(ctx context.Context, _ AddrList) {
// find the next available port
listener, p := nextAvailablePort(p2p.port)
_ = listener.Close()
p2p.port = p
opts := []libp2p.Option{
libp2p.ListenAddrStrings(
fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", p2p.port),
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", p2p.port)),
//libp2p.ListenAddrs(bootstrap...),
}
if p2p.smith.Key.PrivateKey != nil {
opts = append(opts, libp2p.Identity(p2p.smith.Key.PrivateKey))
} else {
// If the seed is zero, let libp2p create a keypair. Otherwise, use a
// deterministic randomness source to make generated keys stay the same
// across multiple runs
if p2p.seed != 0 {
r := mrand.New(mrand.NewSource(p2p.seed))
priv, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
if err != nil {
log.Error("generating KeyPair using ", r)
}
opts = append(opts, libp2p.Identity(priv))
}
}
// Use Secure I/O if requested
if !p2p.secio {
opts = append(opts, libp2p.NoSecurity)
}
var err error
p2p.host, err = libp2p.New(ctx, opts...)
if err != nil {
log.Error("Creating new libp2p host", err)
} else {
log.Noticef("P2P server listening on %d, using pubkey %s", p2p.port, p2p.host.ID())
log.Notice(p2p.host.Addrs())
}
// Build host multi-address
hostAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ipfs/%s", p2p.host.ID().Pretty()))
// Now we can build a full multi-address to reach this host
// by encapsulating both addresses:
p2p.multiAddress = p2p.host.Addrs()[0].Encapsulate(hostAddr)
p2p.host.SetStreamHandler("/pkj/1.0.0", p2p.handleStream)
p2p.host.SetStreamHandler("/pkj/tx/1.0.0", p2p.handlePoolStream)
log.Debugf("Now run on a different terminal:\n./politikojj daemon -bootstrap %s", p2p.multiAddress)
}
func (p2p *ServerP2P) isCitizen(peer peer.AddrInfo) bool {
pubkey := fmt.Sprintf("%v", peer.ID)
for _, v := range p2p.chain.GetHead().Demos.PublicKeysByRole["Citizen"] {
if pubkey == v.Link {
return true
}
}
return false
}
func (p2p *ServerP2P) RunMDNS(ctx context.Context, RendezVousPoint string) {
h := p2p.host
p2p.peerChan = initMDNS(ctx, h, RendezVousPoint)
go p2p.runBroadcaster()
for {
log.Infof("%d waiting for peerInfo ", p2p.port)
peerInfo := <-p2p.peerChan
// will block until we discover a peerInfo
log.Infof("%d ✓ Found peer %s", p2p.port, peerInfo)
if !p2p.isCitizen(peerInfo) {
log.Warningf("%d Refusing connection from %s, the peer is not a citizen", p2p.port, peerInfo)
continue
}
if err := h.Connect(ctx, peerInfo); err != nil {
log.Infof("%d Connection failed: %s", p2p.port, err)
continue
}
// open a stream, this stream will be handled by the router on the other end
if stream, err := h.NewStream(ctx, peerInfo.ID, "/pkj/1.0.0"); err != nil {
log.Info("Stream open failed", err)
_ = stream.Close()
} else {
log.Infof("\u001B[32m%d ✓ Connected to:\u001B[0m %s", p2p.port, peerInfo)
p2p.handleBlockStream(stream, "RunMDNS")
}
// Open A document stream
if streamDoc, err := h.NewStream(ctx, peerInfo.ID, "/pkj/tx/1.0.0"); err != nil {
log.Info("Stream open failed", err)
_ = streamDoc.Close()
} else {
p2p.handlePoolStream(streamDoc)
}
}
}
// A new type we need for writing a custom flag parser
type AddrList []multiaddr.Multiaddr
func (al *AddrList) String() string {
strs := make([]string, len(*al))
for i, addr := range *al {
strs[i] = addr.String()
}
return strings.Join(strs, ",")
}
func (al *AddrList) Set(value string) error {
addr, err := multiaddr.NewMultiaddr(value)
if err != nil {
return err
}
*al = append(*al, addr)
return nil
}
func StringsToAddrs(addrStrings []string) (maddrs []multiaddr.Multiaddr, err error) {
for _, addrString := range addrStrings {
addr, err := multiaddr.NewMultiaddr(addrString)
if err != nil {
return maddrs, err
}
maddrs = append(maddrs, addr)
}
return
}
package network
import (
"context"
"github.com/libp2p/go-libp2p/p2p/discovery"
"time"
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/peer"
)
type discoveryNotifee struct {
PeerChan chan peer.AddrInfo
}
//interface to be called when new peer is found
func (n *discoveryNotifee) HandlePeerFound(pi peer.AddrInfo) {
n.PeerChan <- pi
}
//Initialize the MDNS service
func initMDNS(ctx context.Context, peerhost host.Host, rendezvous string) chan peer.AddrInfo {
// An hour might be a long long period in practical applications. But this is fine for us
mdns, err := discovery.NewMdnsService(ctx, peerhost, time.Hour, rendezvous)
if err != nil {
panic(err)
}
//register with service so that we get notified about peer discovery
n := &discoveryNotifee{}
n.PeerChan = make(chan peer.AddrInfo)
mdns.RegisterNotifee(n)
return n.PeerChan
}
package network
import (
"encoding/json"
"fmt"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/op/go-logging"
"gitlab.com/bertrandbenj/politikojj/chain"
"gitlab.com/bertrandbenj/politikojj/crypto"
"gitlab.com/bertrandbenj/politikojj/model"
"gitlab.com/bertrandbenj/politikojj/node"
"gitlab.com/bertrandbenj/politikojj/storage"
"gitlab.com/bertrandbenj/politikojj/utilities"
"io"
"net"
"net/http"
"strconv"
"time"
)
var log = logging.MustGetLogger("network")
type RestService struct {
chain *chain.BlockChainService
store storage.Store
smith *node.BlockSmith
}
func NewRest(smith *node.BlockSmith) *RestService {
RestAPI := RestService{
chain: smith.Chain,
store: smith.Chain.Store,
smith: smith}
return &RestAPI
}
func (rest RestService) makeMuxRouter() http.Handler {
muxRouter := mux.NewRouter()
muxRouter.HandleFunc("/favicon.ico", rest.FileHandler).Methods("GET")
//muxRouter.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static/"))))
// Chain API
muxRouter.HandleFunc("/chain", rest.GetBlockchain).Methods("GET")
muxRouter.HandleFunc("/block", rest.PostBlock).Methods("POST")
muxRouter.HandleFunc("/block/{id:[0-9]+}", rest.GetBlock).Methods("GET")
muxRouter.HandleFunc("/head", rest.GetHeadCid).Methods("GET")
muxRouter.HandleFunc("/head/demos", rest.GetDemos).Methods("GET")
muxRouter.HandleFunc("/head/cratos", rest.GetCratos).Methods("GET")
muxRouter.HandleFunc("/head/currency", rest.GetCCY).Methods("GET")
// Claim API
muxRouter.HandleFunc("/claim/role", rest.GetClaimRole).Methods("GET")
muxRouter.HandleFunc("/claim/approvedRole", rest.GetApprovedClaimRole).Methods("GET")
muxRouter.HandleFunc("/claim/approve", rest.PostApprovedClaim).Methods("POST")
muxRouter.HandleFunc("/claim/role", rest.PostClaimRole).Methods("POST")
// Cratos API
muxRouter.HandleFunc("/rbac", rest.GetRBAC).Methods("GET")
muxRouter.HandleFunc("/vote", rest.PostVote).Methods("POST")
muxRouter.HandleFunc("/amend", rest.PostAmendment).Methods("POST")
// Tx API
muxRouter.HandleFunc("/tx/send", rest.PostTX).Methods("POST")
//muxRouter.HandleFunc("/tx/of/{pubkey:[0-9a-Z]+}", rest.GetTxOf).Methods("GET")
// Network API
muxRouter.HandleFunc("/network", rest.GetNetwork).Methods("GET")
// Extra
muxRouter.HandleFunc("/keygen", rest.HandleKeyGen).Methods("POST")
muxRouter.HandleFunc("/pools", rest.GetPools).Methods("GET")
muxRouter.HandleFunc("/available", rest.GetAvailables).Methods("GET")
return muxRouter
}
func (rest RestService) GetPools(w http.ResponseWriter, r *http.Request) {
rest.respondObjectAsJson(w, rest.smith.Pools)
}
func (rest RestService) FileHandler(w http.ResponseWriter, r *http.Request) {
log.Info("FileHandler")
//TODO make this generic
http.ServeFile(w, r, "static/favicon.ico")
}
func (rest RestService) StartupRest(port int, pem string, crt string) (io.Closer, error) {
//InitEndpoint()
//InitPool()
muxRoutes := rest.makeMuxRouter()
// Where ORIGIN_ALLOWED is like `scheme://dns[:port]`, or `*` (insecure)
headersOk := handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Accept"})
originsOk := handlers.AllowedOrigins([]string{"*"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})
var listener net.Listener
srv := &http.Server{
Addr: ":" + strconv.Itoa(port),
Handler: handlers.CORS(originsOk, headersOk, methodsOk)(muxRoutes),
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
listener, p := nextAvailablePort(port)
log.Noticef("Rest server listening on %d", p)
go func() {
if pem != "" && crt != "" {
err := srv.ServeTLS(tcpKeepAliveListener{TCPListener: listener.(*net.TCPListener)}, crt, pem)
if err != nil {
log.Info("HTTPS Server Error - ", err)
}
} else {
err := srv.Serve(tcpKeepAliveListener{TCPListener: listener.(*net.TCPListener)})
if err != nil {
log.Info("HTTP Server Error - ", err)
}
}
}()
return listener, nil
}
func nextAvailablePort(port int) (net.Listener, int) {
for i := port; i < port+10; i++ {
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", i))
if err != nil {
continue
} else {
if port != i {
log.Warningf("using port %d since port %d already in use, please check your environment variable and command line parameters ", i, port)
}
return listener, i
}
}
return nil, 0
}
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (rest RestService) HandleKeyGen(w http.ResponseWriter, r *http.Request) {
name := r.FormValue("name")
comment := r.FormValue("comment")
email := r.FormValue("email")
log.Info("genKey : ", name, comment, email)
key := crypto.GenEd25519()
bytes, err := json.MarshalIndent(key.Serialize(), "", " ")
if err != nil {
log.Error("issue responding ")
}
_, _ = w.Write(bytes)
}
// ================= Some generic functions for the rest Server =================
func (rest RestService) DecodeReceivedJSON(w http.ResponseWriter, r *http.Request, dataObject interface{}) (ok bool) {
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&dataObject); err != nil {
log.Error("Decoding", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("HTTP 500: Internal Server Error decoding : " + err.Error()))
return false
}
utilities.DeferCloses(r.Body, "Received JSON")
return true
}
func (rest RestService) respondObjectAsJson(w http.ResponseWriter, data interface{}) {
cl, err := json.MarshalIndent(data, "", "\t")
if err != nil {
log.Error("marshalling claim pool ", err)
w.WriteHeader(http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(cl)
}
func (rest RestService) VerifySignable(w http.ResponseWriter, signable model.Signable) (ok bool) {
pk, signature := signable.Details()
pubK := crypto.ParsePublicKey(pk)
if ok, err := pubK.Verify(signable.PartToSign(), signature); !ok {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("HTTP 500: Signature invalid"))
log.Error("Verifying Signature for string ", err)
return false
}
return true
}
func (rest RestService) StoreToIPFS(w http.ResponseWriter, payload interface{}) (cid string, ok bool) {
cid, err := rest.store.PutObject(payload)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("HTTP 500: Saving to IPFS"))
return "", false
}
return cid, true
}
func (rest RestService) GetAvailables(w http.ResponseWriter, r *http.Request) {
var Available struct {
Votes []string
VariableType []string
TaxType []string
Propose []string
}
Available.Votes = model.AvailableVotes
Available.TaxType = model.AvailableTaxType
Available.VariableType = model.AvailableVariableTax
Available.Propose = model.AvailablePropose
rest.respondObjectAsJson(w, &Available)
}
package network
import (
"gitlab.com/bertrandbenj/politikojj/chain"
"gitlab.com/bertrandbenj/politikojj/model"
"gitlab.com/bertrandbenj/politikojj/node"
"gitlab.com/bertrandbenj/politikojj/storage"
"net"
"strconv"
)
type TCPService struct {
smith *node.BlockSmith
chain *chain.BlockChainService
store storage.Store
port int
bcServer chan model.Block
}
func NewTCP(smith *node.BlockSmith, port int) *TCPService {
return &TCPService{
smith: smith,
chain: smith.Chain,
store: smith.Chain.Store,
port: port,
bcServer: make(chan model.Block),
}
}
func (tcp TCPService) StartupTCP() {
// find the next available port
listener, p := nextAvailablePort(tcp.port)
_ = listener.Close()
tcp.port = p
// start TCP and serve TCP server
server, err := net.Listen("tcp", ":"+strconv.Itoa(tcp.port))
if err != nil {
log.Error(err)
} else {
log.Infof("TCP server listening on %s", server.Addr())
}
defer server.Close()
for {
conn, err := server.Accept()
if err != nil {
log.Error(err)
}
go tcp.handleConn(conn)
}
}
func (tcp TCPService) handleConn(conn net.Conn) {
if conn == nil {
return
}
defer conn.Close()
//utilities.DeferClose(conn, "TCP connection to "+conn.RemoteAddr().String())
}
package network
import (
"gitlab.com/bertrandbenj/politikojj/model"
"net/http"
)
func (rest RestService) PostTX(w http.ResponseWriter, r *http.Request) {
var tx model.Transaction
if rest.DecodeReceivedJSON(w, r, &tx) {
if rest.VerifySignable(w, &tx) {
if cid, ok := rest.StoreToIPFS(w, tx); ok {
rest.smith.Pools.Add(cid, tx)
wrappedTX := model.Wrapper{
CID: cid,
Data: tx,
}
rest.respondObjectAsJson(w, wrappedTX)
}
}
}
}
package node
import (
"fmt"
"github.com/mikespook/gorbac"
"github.com/op/go-logging"
"github.com/tevino/abool"
"gitlab.com/bertrandbenj/politikojj/chain"
"gitlab.com/bertrandbenj/politikojj/crypto"
"gitlab.com/bertrandbenj/politikojj/model"
"gitlab.com/bertrandbenj/politikojj/utilities"
"runtime"
"sync"
)
var log = logging.MustGetLogger("Key")
var forgeLock = &sync.Mutex{}
type BlockDataProcessor interface {
ApplyDelta(t int64, oldBlock model.Block, updatedCid chan string, evidences chan model.Evidence, waitG *sync.WaitGroup)
}
type BlockSmith struct {
Key crypto.Keypair
powCores int
CratosP CratosProcessingService
demosP DemosProcessingService
currencyP CurrencyProcessorService
Pools *TemporaryPool
Chain *chain.BlockChainService
lock *sync.Mutex
}
func NewBlockSmith(ch *chain.BlockChainService, pair crypto.Keypair, powCores int) *BlockSmith {
// ==== Find out how many cores are configured to be used ====
if powCores < 1 || powCores > runtime.NumCPU() {
powCores = runtime.NumCPU()
}
p := NewPool()
BlockSmithRes := BlockSmith{
powCores: powCores,
lock: &sync.Mutex{},
Key: pair,
CratosP: CratosProcessingService{
store: ch.Store,
chain: *ch,
Pools: p,
rbac: gorbac.New(),
permissions: gorbac.Permissions{},
},
demosP: DemosProcessingService{
store: ch.Store,
Pools: p,
},
currencyP: CurrencyProcessorService{
store: ch.Store,
chain: *ch,
Pools: p,
},
Chain: ch,
Pools: p,
}
return &BlockSmithRes
}
func (smith BlockSmith) copyPrevArray(new int64, prev [12]int64) [12]int64 {
var newPrevious [12]int64
newPrevious[0] = new
for x := 0; x < 11; x++ {
newPrevious[x+1] = prev[x]
}
return newPrevious
}
func (smith BlockSmith) personalizedPow(oldBlock model.Block) model.PersonalizedAdaptivePoW {
prev := oldBlock.Difficulty
dtSize := oldBlock.Index
if dtSize > model.DtDiffEval {
dtSize = model.DtDiffEval
}
elapsed := float64(oldBlock.Timestamp - prev.PreviousBlockTimes[0])
speed := float64(dtSize) / elapsed
newBasePow := prev.CommonDifficulty
if speed >= model.MaxSpeed {
if (prev.CommonDifficulty+2)%16 == 0 {
newBasePow += 2
} else {
newBasePow += 1
}
}
if speed <= model.MinSpeed {
if prev.CommonDifficulty%16 == 0 {
newBasePow -= 2
} else {
newBasePow -= 1
}
if newBasePow < 0 {
newBasePow = 0
}
}
newPrevious := smith.copyPrevArray(oldBlock.Timestamp, prev.PreviousBlockTimes)
return model.PersonalizedAdaptivePoW{
CommonDifficulty: newBasePow,
PreviousBlockTimes: newPrevious,
PersonalHandicap: nil,
}
}
func (smith BlockSmith) RunBlockSmith() {
log.Info("Running BlockSmith ...")
for {
newBlock, err := smith.GenerateBlock(smith.Chain.HeadBlock())
if err != nil {
log.Error("Generating block", err)
}
err = smith.CheckForkOrChain(newBlock)
if err != nil {
log.Error("Chaining block", err)
}
}
}
func (smith BlockSmith) CheckForkOrChain(block model.Block) error {
if smith.Chain.IsBlockValid(block) {
head := smith.Chain.HeadBlock()
if smith.Chain.IsBlockDeltaValid(block, head) { // standard case block follows our head
_, _, err := smith.Chain.ChainBlock(block)
if err != nil {
log.Error("Chaining block ", err)
return err
}
} else {
if head.Index < block.Index { // new block is ahead of current head
_, _, err := smith.Chain.ChainBlock(block) // naively accept any block // TODO handle forks
if err != nil {
log.Error("Chaining block ", err)
return err
}
} else {
if head.Index == block.Index && head.PoWHash == block.PoWHash {
return PolitikojjError(fmt.Sprintf("block %d already known ", block.Index))
} else {
return PolitikojjError(fmt.Sprintf("block index too old %d compared to local current %d", block.Index, head.Index))
}
}
}
} else {
return PolitikojjError("local integrity of the block")
}
return nil
}
func (smith BlockSmith) GenerateBlock(oldBlock model.Block) (model.Block, error) {
smith.lock.Lock()
var wg sync.WaitGroup
wg.Add(3)
// ======= Build the block =======
updatedDemosChan := make(chan string, 1)
updatedCratosChan := make(chan string, 1)
updatedCurrencyChan := make(chan string, 1)
evidences := make(chan model.Evidence, 100)
t := utilities.Now()
go smith.CratosP.ApplyDelta(t, oldBlock, updatedCratosChan, evidences, &wg)
go smith.demosP.ApplyDelta(t, oldBlock, updatedDemosChan, evidences, &wg)
go smith.currencyP.ApplyDelta(t, oldBlock, updatedCurrencyChan, evidences, &wg)
newBlock := model.Block{
Index: oldBlock.Index + 1,
Network: oldBlock.Network,
SignedStampedDoc: model.SignedStampedDoc{
Timestamp: t,
PublicKey: smith.Key.PubID(),
},
Previous: model.CidLink{
Link: smith.Chain.GetHead().Cid},
Difficulty: smith.personalizedPow(oldBlock),
Cratos: model.CidLink{Link: <-updatedCratosChan},
Demos: model.CidLink{Link: <-updatedDemosChan},
Currency: model.CidLink{Link: <-updatedCurrencyChan},
Evidences: []model.Evidence{}}
wg.Wait()
close(evidences)
log.Warningf("block %d forged, adding %d evidence ... ", newBlock.Index, len(evidences))
for i := range evidences {
newBlock.Evidences = append(newBlock.Evidences, i)
smith.Pools.Remove(i.CidLink.Link)
}
chain.StampAndSign(&newBlock, smith.Key)
//smith.Chain.SignBlock(&newBlock, smith.Key.PrivateKey)
newBlock = smith.CalculatePow(newBlock)
smith.lock.Unlock()
return newBlock, nil
}
func (smith BlockSmith) CalculatePow(block model.Block) model.Block {
resChan := make(chan model.Block, 1)
// ==== Run inside goroutines until a result is found ====
var interrupt = abool.New()
for cpu := 1; cpu <= smith.powCores; cpu++ {
go func(b model.Block, cpu int, c chan model.Block) {
for i := 0; interrupt.IsNotSet(); i++ {
b.Nonce = fmt.Sprintf("0x%.2x%.8x", cpu, i)
b.HashPow()
if b.IsHashValid(b.Difficulty) {
//log.Info("found PoW", cpu, i, b.Nonce, b.PoWHash)
interrupt.Set()
c <- b
}
}
}(block, cpu, resChan)
}
return <-resChan
}
package node
import (
"github.com/mikespook/gorbac"
"gitlab.com/bertrandbenj/politikojj/chain"
"gitlab.com/bertrandbenj/politikojj/model"
"gitlab.com/bertrandbenj/politikojj/storage"
"strconv"
"sync"
)
func (proc CratosProcessingService) IsAuthorized(role string, perm string) bool {
if proc.permissions[perm] != nil {
return proc.rbac.IsGranted(role, proc.permissions[perm], nil)
}
return false
}
type CratosProcessingService struct {
store storage.Store
chain chain.BlockChainService
Pools *TemporaryPool
rbac *gorbac.RBAC
permissions gorbac.Permissions
}
func (proc CratosProcessingService) ApplyDelta(t int64, oldBlock model.Block, updatedCratosCid chan string, evidences chan model.Evidence, waitG *sync.WaitGroup) {
defer waitG.Done()
// === load from CID
var oldDemos model.Demos
proc.store.GetObject(oldBlock.Demos.Link, &oldDemos)
var oldCratos model.Cratos
if proc.store.GetObject(oldBlock.Cratos.Link, &oldCratos) {
// === Apply delta
newCratos := oldCratos
ballotCount := proc.Pools.CountVotes()
proc.ApplyAmendment(ballotCount[model.VoteAmendment], &newCratos, evidences)
proc.ApplyVariableValueChange(&newCratos, oldDemos, evidences)
// === Persist new one
newCid, err := proc.store.PutObject(newCratos)
if err != nil {
log.Error("cratos dag put error ", newCid)
}
updatedCratosCid <- newCid
}
}
func (proc CratosProcessingService) ApplyAmendment(countAmendmentVote map[string]uint64, newCratos *model.Cratos, evidences chan model.Evidence) {
for amendmentCid, v := range countAmendmentVote {
if v >= proc.chain.GetHead().Majority() {
var amendment model.Amendment
if proc.store.GetObject(amendmentCid, &amendment) {
for _, vr := range amendment.Predicates {
var installFunction = model.InstallAmendment[vr.Predicate]
if installFunction == nil {
log.Error("Unknown Installation method for", vr.Predicate)
}
installFunction(&vr, newCratos)
}
}
//Pools.Remove(amendmentCid)
evidences <- model.Evidence{
CidLink: model.CidLink{Link: amendmentCid},
Object: amendment,
}
for voteK, vote := range proc.Pools.GetVotes() {
if vote.VoteObject == amendmentCid {
evidences <- model.Evidence{
CidLink: model.CidLink{Link: voteK},
Object: vote,
}
//Pools.Remove(voteK)
}
}
}
}
}
func (proc CratosProcessingService) ApplyVariableValueChange(newCratos *model.Cratos, demos model.Demos, evidences chan model.Evidence) {
for voteCid, ballot := range proc.Pools.GetVotes() {
switch ballot.Permission {
case model.VoteVariableValue:
variable := newCratos.Variables[ballot.VoteObject]
// Set unvoted value of new members to the value applicable they entered
for _, v := range demos.PublicKeysByRole["Citizen"] {
if (&variable).VotesByPk[v.Link] == nil {
(&variable).VotesByPk[v.Link] = variable.Value
}
}
// Apply the new vote
vValue, _ := strconv.ParseFloat(ballot.VoteValue, 64)
(&variable).VotesByPk[ballot.PublicKey] = vValue
// calculate the new variable value
//vars := newCratos.Variables[ballot.VoteObject]
(&variable).Avg()
(*newCratos).Variables[ballot.VoteObject] = variable
evidences <- model.Evidence{
CidLink: model.CidLink{Link: voteCid},
Object: ballot,
}
break
}
}
}
func (proc CratosProcessingService) DeserializeFromCratos(block model.Cratos) {
// map[RoleId]PermissionIds
var jsonRoles = block.Roles.Permission
// map[RoleId]ParentIds
var rolesInheritance = block.Roles.Inheritance
// Build roles and add them to goRBAC instance
for rid, pids := range jsonRoles {
role := gorbac.NewStdRole(rid)
for _, pid := range pids {
_, ok := proc.permissions[pid]
if !ok {
proc.permissions[pid] = gorbac.NewStdPermission(pid)
}
_ = role.Assign(proc.permissions[pid])
}
_ = proc.rbac.Add(role)
}
// Assign the inheritance relationship
for rid, parents := range rolesInheritance {
if err := proc.rbac.SetParents(rid, parents); err != nil {
log.Error(err)
}
}
}
func (proc CratosProcessingService) SerializeForCratos() model.Roles {
roles := model.Roles{
Permission: make(map[string][]string),
Inheritance: make(map[string][]string)}
WalkHandler := func(r gorbac.Role, parents []string) error {
// WARNING: Don't use goRbac RBAC instance in the handler,
// otherwise it causes deadlock.
permissions := make([]string, 0)
for _, p := range r.(*gorbac.StdRole).Permissions() {
permissions = append(permissions, p.ID())
}
roles.Permission[r.ID()] = permissions
roles.Inheritance[r.ID()] = parents
return nil
}
if err := gorbac.Walk(proc.rbac, WalkHandler); err != nil {
log.Error(err)
}
return roles
}
package node
import (
"gitlab.com/bertrandbenj/politikojj/chain"
"gitlab.com/bertrandbenj/politikojj/model"
"gitlab.com/bertrandbenj/politikojj/storage"
"gitlab.com/bertrandbenj/politikojj/utilities"
"sync"
"time"
)
type CurrencyProcessorService struct {
store storage.Store
chain chain.BlockChainService
Pools *TemporaryPool
}
func IsNewDay(oldTime int64, nowT int64) bool {
y2, m2, d2 := time.Unix(nowT, 0).Date()
y1, m1, d1 := time.Unix(oldTime, 0).Date()
return y2 > y1 || m2 > m1 || d2 > d1
}
func (ccy CurrencyProcessorService) addUniversalDividend(oldBlock model.Block, inflation float64, newCurrency model.Currency, oldCurrency model.Currency) {
var oldDemos model.Demos
if ccy.store.GetObject(oldBlock.Demos.Link, &oldDemos) {
mm := oldCurrency.MonetaryMass
pop := oldDemos.Population
ud, _ := utilities.DailyUbi(oldCurrency, inflation, pop)
for _, v := range oldDemos.CitizensByName {
pk := v.PublicKey[0].Link
newCurrency.Wallets[pk] += uint64(ud)
}
newCurrency.MonetaryMass = mm + (uint64(ud) * pop)
}
}
func (ccy CurrencyProcessorService) ApplyDelta(t int64, oldBlock model.Block, updatedCid chan string, evidences chan model.Evidence, waitG *sync.WaitGroup) {
defer waitG.Done()
// === load from CID
oldCid := oldBlock.Currency.Link
var oldCcy model.Currency
if !ccy.store.GetObject(oldCid, &oldCcy) {
log.Error("couldn't load Currency")
return
}
var oldCratos model.Cratos
if !ccy.store.GetObject(oldBlock.Cratos.Link, &oldCratos) {
log.Error("couldn't load Cratos")
return
}
taxes := oldCratos.Taxes
inflation := oldCratos.Variables["Inflation"].Value.(float64)
// === Apply delta
newCurrency := oldCcy
if IsNewDay(oldBlock.Timestamp, t) {
ccy.addUniversalDividend(oldBlock, inflation, newCurrency, oldCcy)
}
var e []model.Evidence
ccy.Pools.RLock()
for cid, tx := range ccy.Pools.GetTxs() {
if newCurrency.Wallets[tx.PublicKey] >= tx.Amount {
newCurrency.Wallets[tx.PublicKey] -= tx.Amount
totalTaxed := uint64(0)
for _, tax := range taxes {
taxRate := oldCratos.Variables[tax.Variable].Value.(float64)
taxAmount := uint64(taxRate * float64(tx.Amount))
totalTaxed += taxAmount
newCurrency.PublicWallets[tax.Account] += taxAmount
}
newCurrency.Wallets[tx.To] += tx.Amount - totalTaxed
e = append(e,
model.Evidence{
CidLink: model.CidLink{Link: cid},
Object: tx})
}
}
ccy.Pools.RUnlock()
for _, v := range e {
evidences <- v
}
// === Persist new one
newCid, err := ccy.store.PutObject(newCurrency)
if err != nil {
log.Error("Currency dag put error ", oldCid)
}
updatedCid <- newCid
}
package node
import (
"gitlab.com/bertrandbenj/politikojj/model"
"gitlab.com/bertrandbenj/politikojj/storage"
"gitlab.com/bertrandbenj/politikojj/utilities"
"sync"
)
type DemosProcessingService struct {
store storage.Store
Pools *TemporaryPool
}
func (proc DemosProcessingService) ApplyDelta(t int64, oldBlock model.Block, updatedCid chan string, evidences chan model.Evidence, waitG *sync.WaitGroup) {
defer waitG.Done()
// === load from CID
var oldDemos model.Demos
if proc.store.GetObject(oldBlock.Demos.Link, &oldDemos) {
// === Apply delta
newDemos := oldDemos
proc.FindNewCitizens(&newDemos, evidences)
// === Persist new one
newCid, err := proc.store.PutObject(newDemos)
if err != nil {
log.Error("Demos dag put error ", newCid)
}
updatedCid <- newCid
}
}
func (proc DemosProcessingService) FindNewCitizens(demos *model.Demos, evidences chan model.Evidence) {
majority := demos.Population/2 + 1
ballotCount := proc.Pools.CountVotes()
var voteObjectToRemove []string
for k, v := range ballotCount[model.VoteGrantClaim] {
if v >= majority {
var claim model.Claim
if proc.store.GetObject(k, &claim) {
demos.CitizensByName[claim.ChosenName] = model.CitizenOfName{
PublicKey: []model.CidLink{{Link: claim.PublicKey}},
CitizenshipClaim: model.CidLink{Link: k},
}
citizenList := demos.PublicKeysByRole["Citizen"]
citizenList = append(citizenList, model.CidLink{Link: claim.PublicKey})
demos.PublicKeysByRole[model.Citizen] = citizenList
demos.Population++
voteObjectToRemove = append(voteObjectToRemove, k)
}
}
}
proc.Pools.RLock()
defer proc.Pools.RUnlock()
for k, v := range proc.Pools.GetVotes() {
if utilities.Contains(voteObjectToRemove, v.VoteObject) {
evidences <- model.Evidence{
CidLink: model.CidLink{Link: k},
Object: v}
}
}
}
package node
type PolitikojjError string
func (e PolitikojjError) Error() string {
return "Politikojj error: " + string(e)
}
package node
import (
"gitlab.com/bertrandbenj/politikojj/model"
"reflect"
"sync"
)
// ==================== Pools ====================
type TemporaryPool struct {
Claims map[string]model.Claim
ApprovedClaims map[string]model.ApprovedClaim
Votes map[string]model.OpenBallot
Transactions map[string]model.Transaction
Amendments map[string]model.Amendment
sync.RWMutex
updateChannel chan model.Wrapper
}
func NewPool() *TemporaryPool {
return &TemporaryPool{
Claims: make(map[string]model.Claim),
ApprovedClaims: make(map[string]model.ApprovedClaim),
Votes: make(map[string]model.OpenBallot),
Transactions: make(map[string]model.Transaction),
Amendments: make(map[string]model.Amendment),
}
}
func (pool *TemporaryPool) GetVotes() map[string]model.OpenBallot {
pool.RLock()
defer pool.RUnlock()
return pool.Votes
}
func (pool *TemporaryPool) GetTxs() map[string]model.Transaction {
pool.RLock()
defer pool.RUnlock()
return pool.Transactions
}
func (pool *TemporaryPool) CountVotes() map[string]map[string]uint64 {
pool.RLock()
defer pool.RUnlock()
counts := make(map[string]map[string]uint64)
for _, v := range pool.Votes {
if v.Permission != model.VoteVariableValue {
if counts[v.Permission] == nil {
counts[v.Permission] = make(map[string]uint64)
}
counts[v.Permission][v.VoteObject]++
}
}
return counts
}
func (pool *TemporaryPool) Remove(cid string) {
pool.Lock()
defer pool.Unlock()
delete(pool.Claims, cid)
delete(pool.ApprovedClaims, cid)
delete(pool.Votes, cid)
delete(pool.Amendments, cid)
delete(pool.Transactions, cid)
}
func (pool *TemporaryPool) Exists(cid string) bool {
pool.RLock()
defer pool.RUnlock()
if _, ok := pool.Claims[cid]; ok {
return true
}
if _, ok := pool.Votes[cid]; ok {
return true
}
if _, ok := pool.Transactions[cid]; ok {
return true
}
if _, ok := pool.ApprovedClaims[cid]; ok {
return true
}
if _, ok := pool.Amendments[cid]; ok {
return true
}
return false
}
func (pool *TemporaryPool) Add(cid string, data interface{}) {
if pool.Exists(cid) {
log.Warningf("Document is already pooled %s", cid)
return
}
pool.Lock()
defer pool.Unlock()
strType := ""
switch data.(type) {
case model.Claim:
pool.Claims[cid] = data.(model.Claim)
strType = "Claim"
break
case model.ApprovedClaim:
pool.ApprovedClaims[cid] = data.(model.ApprovedClaim)
strType = "ApprovedClaim"
break
case model.OpenBallot:
pool.Votes[cid] = data.(model.OpenBallot)
strType = "OpenBallot"
break
case model.Amendment:
pool.Amendments[cid] = data.(model.Amendment)
strType = "Amendment"
break
case model.Transaction:
pool.Transactions[cid] = data.(model.Transaction)
strType = "Transaction"
break
default:
log.Error("unhandled data type", reflect.TypeOf(data))
return
}
if pool.updateChannel != nil {
wrap := model.Wrapper{
CID: cid,
Type: strType,
Data: data,
}
//log.Info("pushing Doc for broadcast ", wrap)
pool.updateChannel <- wrap
}
}
func (pool *TemporaryPool) Attach(docsChan chan model.Wrapper) {
pool.updateChannel = docsChan
}
package utilities
import (
"github.com/op/go-logging"
"gitlab.com/bertrandbenj/politikojj/model"
"math"
"os"
"time"
"io"
"reflect"
)
var log = logging.MustGetLogger("utilities")
func DeferCloses(x io.Closer, prefix string) {
defer func() {
if err := x.Close(); err != nil {
log.Error("Closing "+prefix, reflect.TypeOf(x), err)
} else {
log.Info("Closed "+prefix, reflect.TypeOf(x))
}
}()
}
func AnnualRateToDailyRate(r float64) float64 {
return math.Pow(1.0+r, 1/365.25) - 1
}
func DailyUbi(ccy model.Currency, inflation float64, pop uint64) (ud uint64, newBase uint8) {
mm := ccy.MonetaryMass
c := AnnualRateToDailyRate(inflation)
prevUd := ccy.Dividend
base := ccy.UnitBase
return DailyUBI(prevUd, c, mm, pop, base)
}
func DailyUBI(lastUD uint64, c float64, mm uint64, pop uint64, base uint8) (ud uint64, newBase uint8) {
//dt := 86400.
//dtReeval := 15778800.
moneyShare := math.Ceil(float64(mm)/math.Pow10(int(base))) / float64(pop)
reEvalUd := uint64(math.Ceil(float64(lastUD) + c*c*moneyShare))
if float64(reEvalUd) >= math.Pow10(5) {
log.Info("Incrementing base", base)
reEvalUd = uint64(math.Round(math.Ceil(float64(reEvalUd) / 10.)))
newBase = base + 1
}
return reEvalUd, base
}
func Contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
// Min returns the smaller of x or y.
func Min(x, y uint32) uint32 {
if x > y {
return y
}
return x
}
func Now() int64 { return time.Now().UTC().Unix() }
func ConfigLogger(lvl string) {
var format = logging.MustStringFormatter(
`%{time:15:04:05.000} %{color}%{level:.4s} ▶ %{shortpkg:.3s}.%{shortfunc:-16s}%{color:reset} %{message}`,
)
backend2 := logging.NewLogBackend(os.Stderr, "", 0)
//logging.SetLevel( backend2.Logger.LogLevel("INFO"),"")
backend2Formatter := logging.NewBackendFormatter(backend2, format)
// Only errors and more severe messages should be sent to backend2
backend2Leveled := logging.AddModuleLevel(backend2Formatter)
LevL, _ := logging.LogLevel(lvl)
backend2Leveled.SetLevel(LevL, "")
logging.SetBackend(backend2Leveled)
}