Skip to content

Instantly share code, notes, and snippets.

@nolash
Last active September 6, 2020 21:47
Show Gist options
  • Save nolash/9434ea24a71474bebcd0341c98685ff4 to your computer and use it in GitHub Desktop.
Save nolash/9434ea24a71474bebcd0341c98685ff4 to your computer and use it in GitHub Desktop.
POC bzzkey over ENR
package main
import (
"bytes"
"crypto/ecdsa"
"flag"
"fmt"
"io"
"math/rand"
"net"
"os"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/p2p/simulations"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
)
var (
verbose = flag.Bool("v", false, "make verbose")
debugLevel = 3
)
func init() {
flag.Parse()
if *verbose {
debugLevel = 4
}
rand.Seed(42)
log.Root().SetHandler(
log.CallerFileHandler(
log.LvlFilterHandler(
log.Lvl(debugLevel),
log.StreamHandler(
os.Stderr, log.TerminalFormat(true),
),
),
),
)
}
// randReader is used to generate predictable keys
type randReader struct {
}
func (r randReader) Read(b []byte) (int, error) {
i, err := rand.Read(b)
return i, err
}
// auxEntry is a control entry to check that an arbitrary record are transmitted to the peer
type auxEntry string
// ENRKey implements enr.Entry
func (a auxEntry) ENRKey() string {
return "aux"
}
func (a auxEntry) EncodeRLP(w io.Writer) error {
log.Debug("in encoderlp aux", "a", a, "p", fmt.Sprintf("%p", &a))
return rlp.Encode(w, (*string)(&a))
}
func (a *auxEntry) DecodeRLP(s *rlp.Stream) error {
byt, err := s.Bytes()
if err != nil {
return err
}
*a = auxEntry(byt)
log.Debug("in decoderlp aux", "a", a, "p", fmt.Sprintf("%p", &a))
return nil
}
// bzzKeyEntry is the entry type to store the bzz key in the enode
type bzzKeyEntry struct {
data []byte
}
// ENRKey implements enr.Entry
func (b bzzKeyEntry) ENRKey() string {
return "bzzkey"
}
// EncodeRLP implements rlp.Encoder
func (b bzzKeyEntry) EncodeRLP(w io.Writer) error {
log.Debug("in encoderlp", "b", b, "p", fmt.Sprintf("%p", &b))
return rlp.Encode(w, &b.data)
}
// DecodeRLP implements rlp.Decoder
func (b *bzzKeyEntry) DecodeRLP(s *rlp.Stream) error {
byt, err := s.Bytes()
if err != nil {
return err
}
b.data = byt
log.Debug("in decoderlp", "b", b, "p", fmt.Sprintf("%p", &b))
return nil
}
func getRelevantEntries(record *enr.Record) map[string]string {
// recover relevant entries in record
var bzzkey bzzKeyEntry
var aux auxEntry
record.Load(&bzzkey)
record.Load(&aux)
return map[string]string{
"bzzkey": hexutil.Encode(bzzkey.data),
"aux": string(aux),
}
}
type bzzMsg struct {
seq uint64
}
func newBzzTestProtocol(r enr.Record) p2p.Protocol {
var bzzkey bzzKeyEntry
var aux auxEntry
r.Load(&bzzkey)
r.Load(&aux)
return p2p.Protocol{
Name: "bzztest",
Version: 1,
Length: 1,
Run: func(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
log.Info("result in protocol", "entries", getRelevantEntries(peer.Node().Record()))
payload := []byte(fmt.Sprintf("%p", peer))
err := rw.WriteMsg(p2p.Msg{
Code: 0,
Size: uint32(len(payload)),
Payload: bytes.NewReader(payload),
})
log.Debug("writemsg attempt", "err", err)
for {
msg, err := rw.ReadMsg()
if err != nil {
return err
}
log.Debug("got msg", "r", peer, "msg", msg)
}
},
Attributes: []enr.Entry{
bzzkey,
aux,
},
}
}
type bzzTestAPI struct {
}
func newBzzTestAPI(client *adapters.RPCDialer) bzzTestAPI {
return bzzTestAPI{}
}
// noopService fills the space of the required service object for creating a simulation node
type noopService struct {
record enr.Record
}
func (n noopService) APIs() []rpc.API {
return []rpc.API{}
}
func (n noopService) Protocols() []p2p.Protocol {
return []p2p.Protocol{
newBzzTestProtocol(n.record),
}
}
func (n noopService) Start(p *p2p.Server) error {
log.Debug("starting noopservice")
return nil
}
func (n noopService) Stop() error {
log.Debug("stopping noopservice")
return nil
}
func noopServiceFunc(s *adapters.ServiceContext) (node.Service, error) {
return noopService{
record: s.Config.Record,
}, nil
}
// newNodeConfig generates a simulations adapter configuration using the enr scheme
func newNodeConfig(pk *ecdsa.PrivateKey, bzzaddr []byte, v []string) (*adapters.NodeConfig, error) {
var record enr.Record
entry_bzzkey := bzzKeyEntry{
data: bzzaddr,
}
record.Set(entry_bzzkey)
entry_aux := auxEntry(v[0])
record.Set(entry_aux)
entry_pubkey := enode.Secp256k1(pk.PublicKey)
record.Set(entry_pubkey)
entry_ip := enr.IP(net.IPv4(127, 0, 0, 1))
record.Set(entry_ip)
entry_tcp := enr.TCP(0)
record.Set(entry_tcp)
err := enode.SignV4(&record, pk)
if err != nil {
return nil, err
}
nod, err := enode.New(enode.ValidSchemes["v4"], &record)
if err != nil {
return nil, err
}
return &adapters.NodeConfig{
ID: nod.ID(),
PrivateKey: pk,
Name: v[0],
Record: record,
Services: []string{"noop"},
}, nil
}
// newNodeConfigParams returns the private key and swarm address to create a node with
func newNodeConfigParams() (*ecdsa.PrivateKey, []byte, error) {
pk, err := ecdsa.GenerateKey(crypto.S256(), randReader{})
if err != nil {
return nil, nil, err
}
pubkeybytes := crypto.FromECDSAPub(&pk.PublicKey)
bzzkeyhash := crypto.Keccak256Hash(pubkeybytes)
log.Info("config pubkey", "bytes", hexutil.Encode(pubkeybytes))
log.Info("config bzz", "bytes", hexutil.Encode(bzzkeyhash.Bytes()))
return pk, bzzkeyhash.Bytes(), nil
}
func main() {
// create node configuations
pk_l, addr_l, err := newNodeConfigParams()
if err != nil {
log.Crit(err.Error())
}
cfg_l, err := newNodeConfig(pk_l, addr_l, []string{"foo"})
if err != nil {
log.Crit(err.Error())
}
pk_r, addr_r, err := newNodeConfigParams()
if err != nil {
log.Crit(err.Error())
}
cfg_r, err := newNodeConfig(pk_r, addr_r, []string{"bar"})
if err != nil {
log.Crit(err.Error())
}
log.Info("addrs", "left", hexutil.Encode(addr_l), "right", hexutil.Encode(addr_r))
log.Debug("privkey left", "key", hexutil.Encode(crypto.FromECDSA(cfg_l.PrivateKey)), "cfg", cfg_l)
log.Debug("privkey right", "key", hexutil.Encode(crypto.FromECDSA(cfg_r.PrivateKey)), "cfg", cfg_r)
// create the nodes
servicesMap := map[string]adapters.ServiceFunc{
"noop": noopServiceFunc,
}
simAdapter := adapters.NewSimAdapter(servicesMap)
// create network
ncfg := &simulations.NetworkConfig{
ID: "enrtest",
DefaultService: "noop",
}
simnet := simulations.NewNetwork(simAdapter, ncfg)
nod_l, err := simnet.NewNodeWithConfig(cfg_l)
if err != nil {
log.Crit(err.Error())
}
nod_r, err := simnet.NewNodeWithConfig(cfg_r)
if err != nil {
log.Crit(err.Error())
}
log.Info("node info left", "n", nod_l.ID())
log.Info("node info right", "n", nod_r.ID())
// start nodes
err = simnet.Start(nod_l.ID())
if err != nil {
log.Crit(err.Error())
}
defer simnet.Stop(nod_l.ID())
err = simnet.Start(nod_r.ID())
if err != nil {
log.Crit(err.Error())
}
defer simnet.Stop(nod_r.ID())
// connect nodes
nid_l := nod_l.ID()
nid_r := nod_r.ID()
err = simnet.Connect(nid_l, nid_r)
if err != nil {
log.Crit(err.Error())
}
// wait a bit to allow connection to complete
time.Sleep(1 * time.Second)
// retrieve results
for _, n := range simnet.GetNodes() {
// get peerinfo available on node
cli, err := n.Client()
if err != nil {
log.Crit(err.Error())
}
var peerInfo []p2p.PeerInfo
err = cli.Call(&peerInfo, "admin_peers")
if err != nil {
log.Crit(err.Error())
}
// decode peerinfo ENR entry
var record enr.Record
enrInfoBytes, err := hexutil.Decode(peerInfo[0].ENR)
if err != nil {
log.Crit(err.Error())
}
err = rlp.DecodeBytes(enrInfoBytes, &record)
if err != nil {
log.Crit(err.Error())
}
results := getRelevantEntries(&record)
log.Info("result in peerinfo", "node", n, "entries", results)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment