mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-24 12:37:16 +00:00
Merge branch 'develop' of github.com:ethereum/go-ethereum into removews
Conflicts: cmd/ethereum/flags.go cmd/mist/flags.go
This commit is contained in:
commit
4cb0bfe939
@ -27,6 +27,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethutil"
|
||||
@ -36,39 +37,41 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
Identifier string
|
||||
KeyRing string
|
||||
DiffTool bool
|
||||
DiffType string
|
||||
KeyStore string
|
||||
StartRpc bool
|
||||
StartWebSockets bool
|
||||
RpcPort int
|
||||
OutboundPort string
|
||||
ShowGenesis bool
|
||||
AddPeer string
|
||||
MaxPeer int
|
||||
GenAddr bool
|
||||
BootNodes string
|
||||
NodeKey *ecdsa.PrivateKey
|
||||
NAT nat.Interface
|
||||
SecretFile string
|
||||
ExportDir string
|
||||
NonInteractive bool
|
||||
Datadir string
|
||||
LogFile string
|
||||
ConfigFile string
|
||||
DebugFile string
|
||||
LogLevel int
|
||||
LogFormat string
|
||||
Dump bool
|
||||
DumpHash string
|
||||
DumpNumber int
|
||||
VmType int
|
||||
ImportChain string
|
||||
SHH bool
|
||||
Dial bool
|
||||
PrintVersion bool
|
||||
Identifier string
|
||||
KeyRing string
|
||||
DiffTool bool
|
||||
DiffType string
|
||||
KeyStore string
|
||||
StartRpc bool
|
||||
StartWebSockets bool
|
||||
RpcListenAddress string
|
||||
RpcPort int
|
||||
OutboundPort string
|
||||
ShowGenesis bool
|
||||
AddPeer string
|
||||
MaxPeer int
|
||||
GenAddr bool
|
||||
BootNodes string
|
||||
NodeKey *ecdsa.PrivateKey
|
||||
NAT nat.Interface
|
||||
SecretFile string
|
||||
ExportDir string
|
||||
NonInteractive bool
|
||||
Datadir string
|
||||
LogFile string
|
||||
ConfigFile string
|
||||
DebugFile string
|
||||
LogLevel int
|
||||
LogFormat string
|
||||
Dump bool
|
||||
DumpHash string
|
||||
DumpNumber int
|
||||
VmType int
|
||||
ImportChain string
|
||||
SHH bool
|
||||
Dial bool
|
||||
PrintVersion bool
|
||||
MinerThreads int
|
||||
)
|
||||
|
||||
// flags specific to cli client
|
||||
@ -92,6 +95,7 @@ func Init() {
|
||||
flag.StringVar(&KeyRing, "keyring", "", "identifier for keyring to use")
|
||||
flag.StringVar(&KeyStore, "keystore", "db", "system to store keyrings: db|file (db)")
|
||||
|
||||
flag.StringVar(&RpcListenAddress, "rpcaddr", "127.0.0.1", "address for json-rpc server to listen on")
|
||||
flag.IntVar(&RpcPort, "rpcport", 8545, "port to start json-rpc server on")
|
||||
flag.BoolVar(&StartRpc, "rpc", false, "start rpc server")
|
||||
flag.BoolVar(&NonInteractive, "y", false, "non-interactive mode (say yes to confirmations)")
|
||||
@ -116,6 +120,7 @@ func Init() {
|
||||
flag.BoolVar(&StartMining, "mine", false, "start dagger mining")
|
||||
flag.BoolVar(&StartJsConsole, "js", false, "launches javascript console")
|
||||
flag.BoolVar(&PrintVersion, "version", false, "prints version number")
|
||||
flag.IntVar(&MinerThreads, "minerthreads", runtime.NumCPU(), "number of miner threads")
|
||||
|
||||
// Network stuff
|
||||
var (
|
||||
@ -132,6 +137,12 @@ func Init() {
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// When the javascript console is started log to a file instead
|
||||
// of stdout
|
||||
if StartJsConsole {
|
||||
LogFile = path.Join(Datadir, "ethereum.log")
|
||||
}
|
||||
|
||||
var err error
|
||||
if NAT, err = nat.Parse(*natstr); err != nil {
|
||||
log.Fatalf("-nat: %v", err)
|
||||
|
@ -62,20 +62,21 @@ func main() {
|
||||
utils.InitConfig(VmType, ConfigFile, Datadir, "ETH")
|
||||
|
||||
ethereum, err := eth.New(ð.Config{
|
||||
Name: p2p.MakeName(ClientIdentifier, Version),
|
||||
KeyStore: KeyStore,
|
||||
DataDir: Datadir,
|
||||
LogFile: LogFile,
|
||||
LogLevel: LogLevel,
|
||||
LogFormat: LogFormat,
|
||||
MaxPeers: MaxPeer,
|
||||
Port: OutboundPort,
|
||||
NAT: NAT,
|
||||
KeyRing: KeyRing,
|
||||
Shh: true,
|
||||
Dial: Dial,
|
||||
BootNodes: BootNodes,
|
||||
NodeKey: NodeKey,
|
||||
Name: p2p.MakeName(ClientIdentifier, Version),
|
||||
KeyStore: KeyStore,
|
||||
DataDir: Datadir,
|
||||
LogFile: LogFile,
|
||||
LogLevel: LogLevel,
|
||||
LogFormat: LogFormat,
|
||||
MaxPeers: MaxPeer,
|
||||
Port: OutboundPort,
|
||||
NAT: NAT,
|
||||
KeyRing: KeyRing,
|
||||
Shh: true,
|
||||
Dial: Dial,
|
||||
BootNodes: BootNodes,
|
||||
NodeKey: NodeKey,
|
||||
MinerThreads: MinerThreads,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@ -113,10 +114,6 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
if StartMining {
|
||||
utils.StartMining(ethereum)
|
||||
}
|
||||
|
||||
if len(ImportChain) > 0 {
|
||||
start := time.Now()
|
||||
err := utils.ImportChain(ethereum, ImportChain)
|
||||
@ -128,11 +125,17 @@ func main() {
|
||||
}
|
||||
|
||||
if StartRpc {
|
||||
utils.StartRpc(ethereum, RpcPort)
|
||||
utils.StartRpc(ethereum, RpcListenAddress, RpcPort)
|
||||
}
|
||||
|
||||
utils.StartEthereum(ethereum)
|
||||
|
||||
fmt.Printf("Welcome to the FRONTIER\n")
|
||||
|
||||
if StartMining {
|
||||
ethereum.Miner().Start()
|
||||
}
|
||||
|
||||
if StartJsConsole {
|
||||
InitJsConsole(ethereum)
|
||||
} else if len(InputFile) > 0 {
|
||||
|
@ -60,6 +60,7 @@ func (self *JSRepl) Start() {
|
||||
if !self.running {
|
||||
self.running = true
|
||||
repllogger.Infoln("init JS Console")
|
||||
|
||||
reader := bufio.NewReader(self.history)
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
|
@ -37,30 +37,30 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
Identifier string
|
||||
KeyRing string
|
||||
KeyStore string
|
||||
StartRpc bool
|
||||
StartWebSockets bool
|
||||
RpcPort int
|
||||
OutboundPort string
|
||||
ShowGenesis bool
|
||||
AddPeer string
|
||||
MaxPeer int
|
||||
GenAddr bool
|
||||
BootNodes string
|
||||
NodeKey *ecdsa.PrivateKey
|
||||
NAT nat.Interface
|
||||
SecretFile string
|
||||
ExportDir string
|
||||
NonInteractive bool
|
||||
Datadir string
|
||||
LogFile string
|
||||
ConfigFile string
|
||||
DebugFile string
|
||||
LogLevel int
|
||||
VmType int
|
||||
MinerThreads int
|
||||
Identifier string
|
||||
KeyRing string
|
||||
KeyStore string
|
||||
StartRpc bool
|
||||
RpcListenAddress string
|
||||
RpcPort int
|
||||
OutboundPort string
|
||||
ShowGenesis bool
|
||||
AddPeer string
|
||||
MaxPeer int
|
||||
GenAddr bool
|
||||
BootNodes string
|
||||
NodeKey *ecdsa.PrivateKey
|
||||
NAT nat.Interface
|
||||
SecretFile string
|
||||
ExportDir string
|
||||
NonInteractive bool
|
||||
Datadir string
|
||||
LogFile string
|
||||
ConfigFile string
|
||||
DebugFile string
|
||||
LogLevel int
|
||||
VmType int
|
||||
MinerThreads int
|
||||
)
|
||||
|
||||
// flags specific to gui client
|
||||
@ -78,6 +78,7 @@ func Init() {
|
||||
flag.StringVar(&Identifier, "id", "", "Custom client identifier")
|
||||
flag.StringVar(&KeyRing, "keyring", "", "identifier for keyring to use")
|
||||
flag.StringVar(&KeyStore, "keystore", "db", "system to store keyrings: db|file (db)")
|
||||
flag.StringVar(&RpcListenAddress, "rpcaddr", "127.0.0.1", "address for json-rpc server to listen on")
|
||||
flag.IntVar(&RpcPort, "rpcport", 8545, "port to start json-rpc server on")
|
||||
flag.BoolVar(&StartRpc, "rpc", true, "start rpc server")
|
||||
flag.BoolVar(&NonInteractive, "y", false, "non-interactive mode (say yes to confirmations)")
|
||||
|
@ -73,7 +73,7 @@ func run() error {
|
||||
utils.KeyTasks(ethereum.KeyManager(), KeyRing, GenAddr, SecretFile, ExportDir, NonInteractive)
|
||||
|
||||
if StartRpc {
|
||||
utils.StartRpc(ethereum, RpcPort)
|
||||
utils.StartRpc(ethereum, RpcListenAddress, RpcPort)
|
||||
}
|
||||
|
||||
gui := NewWindow(ethereum, config, KeyRing, LogLevel)
|
||||
|
@ -159,9 +159,9 @@ func KeyTasks(keyManager *crypto.KeyManager, KeyRing string, GenAddr bool, Secre
|
||||
clilogger.Infof("Main address %x\n", keyManager.Address())
|
||||
}
|
||||
|
||||
func StartRpc(ethereum *eth.Ethereum, RpcPort int) {
|
||||
func StartRpc(ethereum *eth.Ethereum, RpcListenAddress string, RpcPort int) {
|
||||
var err error
|
||||
ethereum.RpcServer, err = rpchttp.NewRpcHttpServer(xeth.New(ethereum), RpcPort)
|
||||
ethereum.RpcServer, err = rpchttp.NewRpcHttpServer(xeth.New(ethereum), RpcListenAddress, RpcPort)
|
||||
if err != nil {
|
||||
clilogger.Errorf("Could not start RPC interface (port %v): %v", RpcPort, err)
|
||||
} else {
|
||||
|
@ -51,8 +51,6 @@ func GenesisBlock(db ethutil.Database) *types.Block {
|
||||
statedb.Sync()
|
||||
genesis.Header().Root = statedb.Root()
|
||||
|
||||
fmt.Printf("+++ genesis +++\nRoot: %x\nHash: %x\n", genesis.Header().Root, genesis.Hash())
|
||||
|
||||
return genesis
|
||||
}
|
||||
|
||||
|
@ -15,11 +15,13 @@ import (
|
||||
|
||||
func DefaultAssetPath() string {
|
||||
var assetPath string
|
||||
pwd, _ := os.Getwd()
|
||||
srcdir := path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist")
|
||||
|
||||
// If the current working directory is the go-ethereum dir
|
||||
// assume a debug build and use the source directory as
|
||||
// asset directory.
|
||||
pwd, _ := os.Getwd()
|
||||
if pwd == path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist") {
|
||||
if pwd == srcdir {
|
||||
assetPath = path.Join(pwd, "assets")
|
||||
} else {
|
||||
switch runtime.GOOS {
|
||||
@ -35,6 +37,13 @@ func DefaultAssetPath() string {
|
||||
assetPath = "."
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the assetPath exists. If not, try the source directory
|
||||
// This happens when binary is run from outside cmd/mist directory
|
||||
if _, err := os.Stat(assetPath); os.IsNotExist(err) {
|
||||
assetPath = path.Join(srcdir, "assets")
|
||||
}
|
||||
|
||||
return assetPath
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ var jsrelogger = logger.NewLogger("JSRE")
|
||||
type JSRE struct {
|
||||
ethereum *eth.Ethereum
|
||||
Vm *otto.Otto
|
||||
pipe *xeth.XEth
|
||||
xeth *xeth.XEth
|
||||
|
||||
events event.Subscription
|
||||
|
||||
@ -67,7 +67,7 @@ func NewJSRE(ethereum *eth.Ethereum) *JSRE {
|
||||
// We have to make sure that, whoever calls this, calls "Stop"
|
||||
go re.mainLoop()
|
||||
|
||||
re.Bind("eth", &JSEthereum{re.pipe, re.Vm, ethereum})
|
||||
re.Bind("eth", &JSEthereum{re.xeth, re.Vm, ethereum})
|
||||
|
||||
re.initStdFuncs()
|
||||
|
||||
@ -113,12 +113,10 @@ func (self *JSRE) mainLoop() {
|
||||
func (self *JSRE) initStdFuncs() {
|
||||
t, _ := self.Vm.Get("eth")
|
||||
eth := t.Object()
|
||||
eth.Set("watch", self.watch)
|
||||
eth.Set("addPeer", self.addPeer)
|
||||
eth.Set("connect", self.connect)
|
||||
eth.Set("require", self.require)
|
||||
eth.Set("stopMining", self.stopMining)
|
||||
eth.Set("startMining", self.startMining)
|
||||
eth.Set("execBlock", self.execBlock)
|
||||
eth.Set("dump", self.dump)
|
||||
eth.Set("export", self.export)
|
||||
}
|
||||
@ -152,7 +150,8 @@ func (self *JSRE) dump(call otto.FunctionCall) otto.Value {
|
||||
}
|
||||
|
||||
statedb := state.New(block.Root(), self.ethereum.Db())
|
||||
v, _ := self.Vm.ToValue(statedb.Dump())
|
||||
|
||||
v, _ := self.Vm.ToValue(statedb.RawDump())
|
||||
|
||||
return v
|
||||
}
|
||||
@ -167,36 +166,7 @@ func (self *JSRE) startMining(call otto.FunctionCall) otto.Value {
|
||||
return v
|
||||
}
|
||||
|
||||
// eth.watch
|
||||
func (self *JSRE) watch(call otto.FunctionCall) otto.Value {
|
||||
addr, _ := call.Argument(0).ToString()
|
||||
var storageAddr string
|
||||
var cb otto.Value
|
||||
var storageCallback bool
|
||||
if len(call.ArgumentList) > 2 {
|
||||
storageCallback = true
|
||||
storageAddr, _ = call.Argument(1).ToString()
|
||||
cb = call.Argument(2)
|
||||
} else {
|
||||
cb = call.Argument(1)
|
||||
}
|
||||
|
||||
if storageCallback {
|
||||
self.objectCb[addr+storageAddr] = append(self.objectCb[addr+storageAddr], cb)
|
||||
|
||||
// event := "storage:" + string(ethutil.Hex2Bytes(addr)) + ":" + string(ethutil.Hex2Bytes(storageAddr))
|
||||
// self.ethereum.EventMux().Subscribe(event, self.changeChan)
|
||||
} else {
|
||||
self.objectCb[addr] = append(self.objectCb[addr], cb)
|
||||
|
||||
// event := "object:" + string(ethutil.Hex2Bytes(addr))
|
||||
// self.ethereum.EventMux().Subscribe(event, self.changeChan)
|
||||
}
|
||||
|
||||
return otto.UndefinedValue()
|
||||
}
|
||||
|
||||
func (self *JSRE) addPeer(call otto.FunctionCall) otto.Value {
|
||||
func (self *JSRE) connect(call otto.FunctionCall) otto.Value {
|
||||
nodeURL, err := call.Argument(0).ToString()
|
||||
if err != nil {
|
||||
return otto.FalseValue()
|
||||
@ -222,22 +192,12 @@ func (self *JSRE) require(call otto.FunctionCall) otto.Value {
|
||||
return t
|
||||
}
|
||||
|
||||
func (self *JSRE) execBlock(call otto.FunctionCall) otto.Value {
|
||||
hash, err := call.Argument(0).ToString()
|
||||
if err != nil {
|
||||
return otto.UndefinedValue()
|
||||
}
|
||||
|
||||
err = utils.BlockDo(self.ethereum, ethutil.Hex2Bytes(hash))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
func (self *JSRE) export(call otto.FunctionCall) otto.Value {
|
||||
if len(call.ArgumentList) == 0 {
|
||||
fmt.Println("err: require file name")
|
||||
return otto.FalseValue()
|
||||
}
|
||||
|
||||
return otto.TrueValue()
|
||||
}
|
||||
|
||||
func (self *JSRE) export(call otto.FunctionCall) otto.Value {
|
||||
fn, err := call.Argument(0).ToString()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
@ -16,7 +16,7 @@ function pp(object) {
|
||||
str += " ]";
|
||||
} else if(typeof(object) === "object") {
|
||||
str += "{ ";
|
||||
var last = Object.keys(object).sort().pop()
|
||||
var last = Object.keys(object).pop()
|
||||
for(var k in object) {
|
||||
str += k + ": " + pp(object[k]);
|
||||
|
||||
|
@ -52,10 +52,5 @@ func (self *Miner) Stop() {
|
||||
}
|
||||
|
||||
func (self *Miner) HashRate() int64 {
|
||||
var tot int64
|
||||
for _, agent := range self.worker.agents {
|
||||
tot += agent.Pow().GetHashrate()
|
||||
}
|
||||
|
||||
return tot
|
||||
return self.worker.HashRate()
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"math/big"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
@ -111,6 +112,8 @@ func (self *worker) register(agent Agent) {
|
||||
func (self *worker) update() {
|
||||
events := self.mux.Subscribe(core.ChainEvent{}, core.NewMinedBlockEvent{})
|
||||
|
||||
timer := time.NewTicker(2 * time.Second)
|
||||
|
||||
out:
|
||||
for {
|
||||
select {
|
||||
@ -129,6 +132,8 @@ out:
|
||||
agent.Stop()
|
||||
}
|
||||
break out
|
||||
case <-timer.C:
|
||||
minerlogger.Debugln("Hash rate:", self.HashRate(), "Khash")
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,3 +249,12 @@ func (self *worker) commitTransaction(tx *types.Transaction) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *worker) HashRate() int64 {
|
||||
var tot int64
|
||||
for _, agent := range self.agents {
|
||||
tot += agent.Pow().GetHashrate()
|
||||
}
|
||||
|
||||
return tot
|
||||
}
|
||||
|
@ -29,8 +29,8 @@ import (
|
||||
var rpchttplogger = logger.NewLogger("RPC-HTTP")
|
||||
var JSON rpc.JsonWrapper
|
||||
|
||||
func NewRpcHttpServer(pipe *xeth.XEth, port int) (*RpcHttpServer, error) {
|
||||
sport := fmt.Sprintf("127.0.0.1:%d", port)
|
||||
func NewRpcHttpServer(pipe *xeth.XEth, address string, port int) (*RpcHttpServer, error) {
|
||||
sport := fmt.Sprintf("%s:%d", address, port)
|
||||
l, err := net.Listen("tcp", sport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -41,6 +41,7 @@ func NewRpcHttpServer(pipe *xeth.XEth, port int) (*RpcHttpServer, error) {
|
||||
quit: make(chan bool),
|
||||
pipe: pipe,
|
||||
port: port,
|
||||
addr: address,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -49,6 +50,7 @@ type RpcHttpServer struct {
|
||||
listener net.Listener
|
||||
pipe *xeth.XEth
|
||||
port int
|
||||
addr string
|
||||
}
|
||||
|
||||
func (s *RpcHttpServer) exitHandler() {
|
||||
@ -69,7 +71,7 @@ func (s *RpcHttpServer) Stop() {
|
||||
}
|
||||
|
||||
func (s *RpcHttpServer) Start() {
|
||||
rpchttplogger.Infof("Starting RPC-HTTP server on port %d", s.port)
|
||||
rpchttplogger.Infof("Starting RPC-HTTP server on %s:%d", s.addr, s.port)
|
||||
go s.exitHandler()
|
||||
|
||||
api := rpc.NewEthereumApi(s.pipe)
|
||||
|
@ -20,7 +20,7 @@ type World struct {
|
||||
Accounts map[string]Account `json:"accounts"`
|
||||
}
|
||||
|
||||
func (self *StateDB) Dump() []byte {
|
||||
func (self *StateDB) RawDump() World {
|
||||
world := World{
|
||||
Root: ethutil.Bytes2Hex(self.trie.Root()),
|
||||
Accounts: make(map[string]Account),
|
||||
@ -39,8 +39,11 @@ func (self *StateDB) Dump() []byte {
|
||||
}
|
||||
world.Accounts[ethutil.Bytes2Hex(it.Key)] = account
|
||||
}
|
||||
return world
|
||||
}
|
||||
|
||||
json, err := json.MarshalIndent(world, "", " ")
|
||||
func (self *StateDB) Dump() []byte {
|
||||
json, err := json.MarshalIndent(self.RawDump(), "", " ")
|
||||
if err != nil {
|
||||
fmt.Println("dump err", err)
|
||||
}
|
||||
|
18
vm/vm.go
18
vm/vm.go
@ -16,6 +16,8 @@ type Vm struct {
|
||||
logStr string
|
||||
|
||||
err error
|
||||
// For logging
|
||||
debug bool
|
||||
|
||||
Dbg Debugger
|
||||
|
||||
@ -32,7 +34,7 @@ func New(env Environment) *Vm {
|
||||
lt = LogTyDiff
|
||||
}
|
||||
|
||||
return &Vm{env: env, logTy: lt, Recoverable: true}
|
||||
return &Vm{debug: false, env: env, logTy: lt, Recoverable: true}
|
||||
}
|
||||
|
||||
func (self *Vm) Run(me, caller ContextRef, code []byte, value, gas, price *big.Int, callData []byte) (ret []byte, err error) {
|
||||
@ -938,17 +940,21 @@ func (self *Vm) RunPrecompiled(p *PrecompiledAccount, callData []byte, context *
|
||||
}
|
||||
|
||||
func (self *Vm) Printf(format string, v ...interface{}) VirtualMachine {
|
||||
if self.logTy == LogTyPretty {
|
||||
self.logStr += fmt.Sprintf(format, v...)
|
||||
if self.debug {
|
||||
if self.logTy == LogTyPretty {
|
||||
self.logStr += fmt.Sprintf(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *Vm) Endl() VirtualMachine {
|
||||
if self.logTy == LogTyPretty {
|
||||
vmlogger.Debugln(self.logStr)
|
||||
self.logStr = ""
|
||||
if self.debug {
|
||||
if self.logTy == LogTyPretty {
|
||||
vmlogger.Debugln(self.logStr)
|
||||
self.logStr = ""
|
||||
}
|
||||
}
|
||||
|
||||
return self
|
||||
|
Loading…
Reference in New Issue
Block a user