mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 06:06:44 +00:00
* add bzz.syncEnabled (hive.syncEnabled -> testapi/SyncEnabled | bzznosync param)) triggers nil sync State request to peer * syncEnabled false then historical items are not delivered * add swap initial scenarios, no funds, no retrieval; chequebook autodeployment; available funds -> retrieval * remove -ipcexp option from swarm script * tweaks to swarm/cmd files * add Balance to chequebook api * set chequebook balance from blockchain * fix chequebook tests after balance check * log balance when chequebook created * fix state setting in ethereum backend * add balanceAt to backend
346 lines
10 KiB
Go
346 lines
10 KiB
Go
package network
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/kademlia"
|
|
"github.com/ethereum/go-ethereum/logger"
|
|
"github.com/ethereum/go-ethereum/logger/glog"
|
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
|
)
|
|
|
|
// Hive is the logistic manager of the swarm
|
|
// it uses a generic kademlia nodetable to find best peer list
|
|
// for any target
|
|
// this is used by the netstore to search for content in the swarm
|
|
// the bzz protocol peersMsgData exchange is relayed to Kademlia
|
|
// for db storage and filtering
|
|
// connections and disconnections are reported and relayed
|
|
// to keep the nodetable uptodate
|
|
|
|
type Hive struct {
|
|
listenAddr func() string
|
|
callInterval uint64
|
|
id discover.NodeID
|
|
addr kademlia.Address
|
|
kad *kademlia.Kademlia
|
|
path string
|
|
toggle chan bool
|
|
more chan bool
|
|
|
|
// for testing only
|
|
swapEnabled bool
|
|
syncEnabled bool
|
|
blockRead bool
|
|
blockWrite bool
|
|
}
|
|
|
|
const (
|
|
callInterval = 10000000000
|
|
bucketSize = 3
|
|
maxProx = 10
|
|
proxBinSize = 8
|
|
)
|
|
|
|
type HiveParams struct {
|
|
CallInterval uint64
|
|
KadDbPath string
|
|
*kademlia.KadParams
|
|
}
|
|
|
|
func NewHiveParams(path string) *HiveParams {
|
|
kad := kademlia.NewKadParams()
|
|
kad.BucketSize = bucketSize
|
|
kad.MaxProx = maxProx
|
|
kad.ProxBinSize = proxBinSize
|
|
|
|
return &HiveParams{
|
|
CallInterval: callInterval,
|
|
KadDbPath: filepath.Join(path, "bzz-peers.json"),
|
|
KadParams: kad,
|
|
}
|
|
}
|
|
|
|
func NewHive(addr common.Hash, params *HiveParams, swapEnabled, syncEnabled bool) *Hive {
|
|
kad := kademlia.New(kademlia.Address(addr), params.KadParams)
|
|
return &Hive{
|
|
callInterval: params.CallInterval,
|
|
kad: kad,
|
|
addr: kad.Addr(),
|
|
path: params.KadDbPath,
|
|
swapEnabled: swapEnabled,
|
|
syncEnabled: syncEnabled,
|
|
}
|
|
}
|
|
|
|
func (self *Hive) SyncEnabled(on bool) {
|
|
self.syncEnabled = on
|
|
}
|
|
|
|
func (self *Hive) SwapEnabled(on bool) {
|
|
self.swapEnabled = on
|
|
}
|
|
|
|
func (self *Hive) BlockNetworkRead(on bool) {
|
|
self.blockRead = on
|
|
}
|
|
|
|
func (self *Hive) BlockNetworkWrite(on bool) {
|
|
self.blockWrite = on
|
|
}
|
|
|
|
// public accessor to the hive base address
|
|
func (self *Hive) Addr() kademlia.Address {
|
|
return self.addr
|
|
}
|
|
|
|
// Start receives network info only at startup
|
|
// listedAddr is a function to retrieve listening address to advertise to peers
|
|
// connectPeer is a function to connect to a peer based on its NodeID or enode URL
|
|
// there are called on the p2p.Server which runs on the node
|
|
func (self *Hive) Start(id discover.NodeID, listenAddr func() string, connectPeer func(string) error) (err error) {
|
|
self.toggle = make(chan bool)
|
|
self.more = make(chan bool)
|
|
self.id = id
|
|
self.listenAddr = listenAddr
|
|
err = self.kad.Load(self.path, nil)
|
|
if err != nil {
|
|
glog.V(logger.Warn).Infof("[BZZ] KΛÐΞMLIΛ Warning: error reading kaddb '%s' (skipping): %v", self.path, err)
|
|
err = nil
|
|
}
|
|
// this loop is doing bootstrapping and maintains a healthy table
|
|
go self.keepAlive()
|
|
go func() {
|
|
// whenever toggled ask kademlia about most preferred peer
|
|
for alive := range self.more {
|
|
if !alive {
|
|
// receiving false closes the loop while allowing parallel routines
|
|
// to attempt to write to more (remove Peer when shutting down)
|
|
return
|
|
}
|
|
node, proxLimit := self.kad.FindBest()
|
|
if node != nil && len(node.Url) > 0 {
|
|
glog.V(logger.Detail).Infof("[BZZ] KΛÐΞMLIΛ hive: call for bee %v", node.Url)
|
|
// enode or any lower level connection address is unnecessary in future
|
|
// discovery table is used to look it up.
|
|
connectPeer(node.Url)
|
|
} else if proxLimit > -1 {
|
|
// a random peer is taken from the table
|
|
peers := self.kad.FindClosest(kademlia.RandomAddressAt(self.addr, rand.Intn(self.kad.MaxProx)), 1)
|
|
if len(peers) > 0 {
|
|
// a random address at prox bin 0 is sent for lookup
|
|
randAddr := kademlia.RandomAddressAt(self.addr, proxLimit)
|
|
req := &retrieveRequestMsgData{
|
|
Key: storage.Key(randAddr[:]),
|
|
}
|
|
glog.V(logger.Detail).Infof("[BZZ] KΛÐΞMLIΛ hive: call any bee in area %v messenger bee %v", randAddr, peers[0])
|
|
peers[0].(*peer).retrieve(req)
|
|
}
|
|
self.toggle <- true
|
|
glog.V(logger.Detail).Infof("[BZZ] KΛÐΞMLIΛ hive: buzz kept alive")
|
|
} else {
|
|
self.toggle <- false
|
|
}
|
|
glog.V(logger.Detail).Infof("[BZZ] KΛÐΞMLIΛ hive: queen's address: %v, population: %d (%d)\n%v", self.addr, self.kad.Count(), self.kad.DBCount(), self.kad)
|
|
}
|
|
}()
|
|
return
|
|
}
|
|
|
|
// keepAlive is a forever loop
|
|
// in its awake state it periodically triggers connection attempts
|
|
// by writing to self.more until Kademlia Table is saturated
|
|
// wake state is toggled by writing to self.toggle
|
|
// it restarts if the table becomes non-full again due to disconnections
|
|
func (self *Hive) keepAlive() {
|
|
alarm := time.NewTicker(time.Duration(self.callInterval)).C
|
|
for {
|
|
select {
|
|
case <-alarm:
|
|
if self.kad.DBCount() > 0 {
|
|
select {
|
|
case self.more <- true:
|
|
default:
|
|
}
|
|
}
|
|
case need, alive := <-self.toggle:
|
|
if !alive {
|
|
self.more <- false
|
|
return
|
|
}
|
|
if alarm == nil && need {
|
|
alarm = time.NewTicker(time.Duration(self.callInterval)).C
|
|
}
|
|
if alarm != nil && !need {
|
|
alarm = nil
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (self *Hive) Stop() error {
|
|
// closing toggle channel quits the updateloop
|
|
close(self.toggle)
|
|
return self.kad.Save(self.path, saveSync)
|
|
}
|
|
|
|
// called at the end of a successful protocol handshake
|
|
func (self *Hive) addPeer(p *peer) {
|
|
glog.V(logger.Detail).Infof("[BZZ] KΛÐΞMLIΛ hive: hi new bee %v", p)
|
|
self.kad.On(p, loadSync)
|
|
// self lookup (can be encoded as nil/zero key since peers addr known) + no id ()
|
|
// the most common way of saying hi in bzz is initiation of gossip
|
|
// let me know about anyone new from my hood , here is the storageradius
|
|
// to send the 6 byte self lookup
|
|
// we do not record as request or forward it, just reply with peers
|
|
p.retrieve(&retrieveRequestMsgData{})
|
|
glog.V(logger.Detail).Infof("[BZZ] KΛÐΞMLIΛ hive: 'whatsup wheresdaparty' sent to %v", p)
|
|
select {
|
|
case self.more <- true:
|
|
default:
|
|
}
|
|
}
|
|
|
|
// called after peer disconnected
|
|
func (self *Hive) removePeer(p *peer) {
|
|
glog.V(logger.Detail).Infof("[BZZ] KΛÐΞMLIΛ hive: bee %v gone offline", p)
|
|
self.kad.Off(p, saveSync)
|
|
select {
|
|
case self.more <- true:
|
|
default:
|
|
}
|
|
if self.kad.Count() == 0 {
|
|
glog.V(logger.Detail).Infof("[BZZ] KΛÐΞMLIΛ hive: empty, all bees gone", p)
|
|
}
|
|
}
|
|
|
|
// Retrieve a list of live peers that are closer to target than us
|
|
func (self *Hive) getPeers(target storage.Key, max int) (peers []*peer) {
|
|
var addr kademlia.Address
|
|
copy(addr[:], target[:])
|
|
for _, node := range self.kad.FindClosest(addr, max) {
|
|
peers = append(peers, node.(*peer))
|
|
}
|
|
return
|
|
}
|
|
|
|
// disconnects all the peers
|
|
func (self *Hive) DropAll() {
|
|
glog.V(logger.Detail).Infof("[BZZ] KΛÐΞMLIΛ hive: dropping all bees")
|
|
for _, node := range self.kad.FindClosest(kademlia.Address{}, 0) {
|
|
node.Drop()
|
|
}
|
|
}
|
|
|
|
// contructor for kademlia.NodeRecord based on peer address alone
|
|
// TODO: should go away and only addr passed to kademlia
|
|
func newNodeRecord(addr *peerAddr) *kademlia.NodeRecord {
|
|
now := kademlia.Time(time.Now())
|
|
return &kademlia.NodeRecord{
|
|
Addr: addr.Addr,
|
|
Url: addr.String(),
|
|
Seen: now,
|
|
After: now,
|
|
}
|
|
}
|
|
|
|
// called by the protocol when receiving peerset (for target address)
|
|
// peersMsgData is converted to a slice of NodeRecords for Kademlia
|
|
// this is to store all thats needed
|
|
func (self *Hive) HandlePeersMsg(req *peersMsgData, from *peer) {
|
|
var nrs []*kademlia.NodeRecord
|
|
for _, p := range req.Peers {
|
|
nrs = append(nrs, newNodeRecord(p))
|
|
}
|
|
self.kad.Add(nrs)
|
|
}
|
|
|
|
// peer wraps the protocol instance to represent a connected peer
|
|
// it implements kademlia.Node interface
|
|
type peer struct {
|
|
*bzz // protocol instance running on peer connection
|
|
}
|
|
|
|
// protocol instance implements kademlia.Node interface (embedded peer)
|
|
func (self *peer) Addr() kademlia.Address {
|
|
return self.remoteAddr.Addr
|
|
}
|
|
|
|
func (self *peer) Url() string {
|
|
return self.remoteAddr.String()
|
|
}
|
|
|
|
// TODO take into account traffic
|
|
func (self *peer) LastActive() time.Time {
|
|
return time.Now()
|
|
}
|
|
|
|
// reads the serialised form of sync state persisted as the 'Meta' attribute
|
|
// and sets the decoded syncState on the online node
|
|
func loadSync(record *kademlia.NodeRecord, node kademlia.Node) error {
|
|
if p, ok := node.(*peer); ok {
|
|
if record.Meta == nil {
|
|
glog.V(logger.Debug).Infof("no sync state for node record %v", record)
|
|
return nil
|
|
}
|
|
state, err := decodeSync(record.Meta)
|
|
glog.V(logger.Debug).Infof("sync state for node record %v: %s -> %v", record, string(*(record.Meta)), state)
|
|
p.syncState = state
|
|
return err
|
|
}
|
|
return fmt.Errorf("invalid type")
|
|
}
|
|
|
|
// callback when saving a sync state
|
|
func saveSync(record *kademlia.NodeRecord, node kademlia.Node) {
|
|
if p, ok := node.(*peer); ok {
|
|
meta, err := encodeSync(p.syncState)
|
|
if err != nil {
|
|
glog.V(logger.Warn).Infof("error saving sync state for %v: %v", node, err)
|
|
return
|
|
}
|
|
glog.V(logger.Warn).Infof("saving sync state for %v: %s", node, string(*meta))
|
|
|
|
record.Meta = meta
|
|
}
|
|
}
|
|
|
|
// the immediate response to a retrieve request,
|
|
// sends relevant peer data given by the kademlia hive to the requester
|
|
// TODO: remember peers sent for duration of the session, only new peers sent
|
|
func (self *Hive) peers(req *retrieveRequestMsgData) {
|
|
// FIXME: should check req.MaxPeers but then should not default to zero or make sure we set it when sending retrieveRequests
|
|
// we might need chunk.req to cache relevant peers response,
|
|
// hive change would expire it
|
|
if req != nil && req.MaxPeers >= 0 {
|
|
var addrs []*peerAddr
|
|
if req.timeout == nil || time.Now().Before(*(req.timeout)) {
|
|
key := req.Key
|
|
// self lookup from remote peer
|
|
if storage.IsZeroKey(key) {
|
|
addr := req.from.Addr()
|
|
key = storage.Key(addr[:])
|
|
req.Key = nil
|
|
}
|
|
// get peer addresses from hive
|
|
for _, peer := range self.getPeers(key, int(req.MaxPeers)) {
|
|
addrs = append(addrs, peer.remoteAddr)
|
|
}
|
|
glog.V(logger.Detail).Infof("[BZZ] Hive sending %d peer addresses to %v. req.Id: %v, req.Key: %x", len(addrs), req.from, req.Id, req.Key.Log())
|
|
|
|
peersData := &peersMsgData{
|
|
Peers: addrs,
|
|
Key: req.Key,
|
|
Id: req.Id,
|
|
}
|
|
peersData.setTimeout(req.timeout)
|
|
req.from.peers(peersData)
|
|
}
|
|
}
|
|
}
|