Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
G
Geth-Modification
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张蕾
Geth-Modification
Commits
0bedf1c3
Commit
0bedf1c3
authored
May 11, 2015
by
Jeffrey Wilcke
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #918 from obscuren/cpu_miner_fixes
cmd/geth, miner, backend, xeth: Fixed miner threads to be settable
parents
59bc5412
48bd4887
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
50 additions
and
46 deletions
+50
-46
admin.go
cmd/geth/admin.go
+2
-3
main.go
cmd/geth/main.go
+1
-1
ui_lib.go
cmd/mist/ui_lib.go
+1
-1
backend.go
eth/backend.go
+6
-6
downloader.go
eth/downloader/downloader.go
+2
-2
sync.go
eth/sync.go
+2
-1
agent.go
miner/agent.go
+11
-11
miner.go
miner/miner.go
+16
-17
worker.go
miner/worker.go
+6
-1
api.go
rpc/api.go
+1
-1
xeth.go
xeth/xeth.go
+2
-2
No files found.
cmd/geth/admin.go
View file @
0bedf1c3
...
@@ -275,14 +275,13 @@ func (js *jsre) verbosity(call otto.FunctionCall) otto.Value {
...
@@ -275,14 +275,13 @@ func (js *jsre) verbosity(call otto.FunctionCall) otto.Value {
}
}
func
(
js
*
jsre
)
startMining
(
call
otto
.
FunctionCall
)
otto
.
Value
{
func
(
js
*
jsre
)
startMining
(
call
otto
.
FunctionCall
)
otto
.
Value
{
_
,
err
:=
call
.
Argument
(
0
)
.
ToInteger
()
threads
,
err
:=
call
.
Argument
(
0
)
.
ToInteger
()
if
err
!=
nil
{
if
err
!=
nil
{
fmt
.
Println
(
err
)
fmt
.
Println
(
err
)
return
otto
.
FalseValue
()
return
otto
.
FalseValue
()
}
}
// threads now ignored
err
=
js
.
ethereum
.
StartMining
()
err
=
js
.
ethereum
.
StartMining
(
int
(
threads
)
)
if
err
!=
nil
{
if
err
!=
nil
{
fmt
.
Println
(
err
)
fmt
.
Println
(
err
)
return
otto
.
FalseValue
()
return
otto
.
FalseValue
()
...
...
cmd/geth/main.go
View file @
0bedf1c3
...
@@ -401,7 +401,7 @@ func startEth(ctx *cli.Context, eth *eth.Ethereum) {
...
@@ -401,7 +401,7 @@ func startEth(ctx *cli.Context, eth *eth.Ethereum) {
}
}
}
}
if
ctx
.
GlobalBool
(
utils
.
MiningEnabledFlag
.
Name
)
{
if
ctx
.
GlobalBool
(
utils
.
MiningEnabledFlag
.
Name
)
{
if
err
:=
eth
.
StartMining
();
err
!=
nil
{
if
err
:=
eth
.
StartMining
(
ctx
.
GlobalInt
(
utils
.
MinerThreadsFlag
.
Name
)
);
err
!=
nil
{
utils
.
Fatalf
(
"%v"
,
err
)
utils
.
Fatalf
(
"%v"
,
err
)
}
}
}
}
...
...
cmd/mist/ui_lib.go
View file @
0bedf1c3
...
@@ -159,7 +159,7 @@ func (self *UiLib) RemoveLocalTransaction(id int) {
...
@@ -159,7 +159,7 @@ func (self *UiLib) RemoveLocalTransaction(id int) {
func
(
self
*
UiLib
)
ToggleMining
()
bool
{
func
(
self
*
UiLib
)
ToggleMining
()
bool
{
if
!
self
.
eth
.
IsMining
()
{
if
!
self
.
eth
.
IsMining
()
{
err
:=
self
.
eth
.
StartMining
()
err
:=
self
.
eth
.
StartMining
(
4
)
return
err
==
nil
return
err
==
nil
}
else
{
}
else
{
self
.
eth
.
StopMining
()
self
.
eth
.
StopMining
()
...
...
eth/backend.go
View file @
0bedf1c3
...
@@ -267,7 +267,7 @@ func New(config *Config) (*Ethereum, error) {
...
@@ -267,7 +267,7 @@ func New(config *Config) (*Ethereum, error) {
eth
.
txPool
=
core
.
NewTxPool
(
eth
.
EventMux
(),
eth
.
chainManager
.
State
,
eth
.
chainManager
.
GasLimit
)
eth
.
txPool
=
core
.
NewTxPool
(
eth
.
EventMux
(),
eth
.
chainManager
.
State
,
eth
.
chainManager
.
GasLimit
)
eth
.
blockProcessor
=
core
.
NewBlockProcessor
(
stateDb
,
extraDb
,
eth
.
pow
,
eth
.
txPool
,
eth
.
chainManager
,
eth
.
EventMux
())
eth
.
blockProcessor
=
core
.
NewBlockProcessor
(
stateDb
,
extraDb
,
eth
.
pow
,
eth
.
txPool
,
eth
.
chainManager
,
eth
.
EventMux
())
eth
.
chainManager
.
SetProcessor
(
eth
.
blockProcessor
)
eth
.
chainManager
.
SetProcessor
(
eth
.
blockProcessor
)
eth
.
miner
=
miner
.
New
(
eth
,
eth
.
pow
,
config
.
MinerThreads
)
eth
.
miner
=
miner
.
New
(
eth
,
eth
.
pow
)
eth
.
miner
.
SetGasPrice
(
config
.
GasPrice
)
eth
.
miner
.
SetGasPrice
(
config
.
GasPrice
)
eth
.
protocolManager
=
NewProtocolManager
(
config
.
ProtocolVersion
,
config
.
NetworkId
,
eth
.
eventMux
,
eth
.
txPool
,
eth
.
chainManager
,
eth
.
downloader
)
eth
.
protocolManager
=
NewProtocolManager
(
config
.
ProtocolVersion
,
config
.
NetworkId
,
eth
.
eventMux
,
eth
.
txPool
,
eth
.
chainManager
,
eth
.
downloader
)
...
@@ -368,7 +368,7 @@ func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) {
...
@@ -368,7 +368,7 @@ func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) {
s
.
chainManager
.
ResetWithGenesisBlock
(
gb
)
s
.
chainManager
.
ResetWithGenesisBlock
(
gb
)
}
}
func
(
s
*
Ethereum
)
StartMining
()
error
{
func
(
s
*
Ethereum
)
StartMining
(
threads
int
)
error
{
eb
,
err
:=
s
.
Etherbase
()
eb
,
err
:=
s
.
Etherbase
()
if
err
!=
nil
{
if
err
!=
nil
{
err
=
fmt
.
Errorf
(
"Cannot start mining without etherbase address: %v"
,
err
)
err
=
fmt
.
Errorf
(
"Cannot start mining without etherbase address: %v"
,
err
)
...
@@ -376,7 +376,7 @@ func (s *Ethereum) StartMining() error {
...
@@ -376,7 +376,7 @@ func (s *Ethereum) StartMining() error {
return
err
return
err
}
}
go
s
.
miner
.
Start
(
eb
)
go
s
.
miner
.
Start
(
eb
,
threads
)
return
nil
return
nil
}
}
...
@@ -461,13 +461,13 @@ done:
...
@@ -461,13 +461,13 @@ done:
case
<-
ticker
.
C
:
case
<-
ticker
.
C
:
// don't change the order of database flushes
// don't change the order of database flushes
if
err
:=
s
.
extraDb
.
Flush
();
err
!=
nil
{
if
err
:=
s
.
extraDb
.
Flush
();
err
!=
nil
{
glog
.
Fatalf
(
"fatal error: flush extraDb: %v
\n
"
,
err
)
glog
.
Fatalf
(
"fatal error: flush extraDb: %v
(Restart your node. We are aware of this issue)
\n
"
,
err
)
}
}
if
err
:=
s
.
stateDb
.
Flush
();
err
!=
nil
{
if
err
:=
s
.
stateDb
.
Flush
();
err
!=
nil
{
glog
.
Fatalf
(
"fatal error: flush stateDb: %v
\n
"
,
err
)
glog
.
Fatalf
(
"fatal error: flush stateDb: %v
(Restart your node. We are aware of this issue)
\n
"
,
err
)
}
}
if
err
:=
s
.
blockDb
.
Flush
();
err
!=
nil
{
if
err
:=
s
.
blockDb
.
Flush
();
err
!=
nil
{
glog
.
Fatalf
(
"fatal error: flush blockDb: %v
\n
"
,
err
)
glog
.
Fatalf
(
"fatal error: flush blockDb: %v
(Restart your node. We are aware of this issue)
\n
"
,
err
)
}
}
case
<-
s
.
shutdownChan
:
case
<-
s
.
shutdownChan
:
break
done
break
done
...
...
eth/downloader/downloader.go
View file @
0bedf1c3
...
@@ -28,7 +28,7 @@ var (
...
@@ -28,7 +28,7 @@ var (
errUnknownPeer
=
errors
.
New
(
"peer's unknown or unhealthy"
)
errUnknownPeer
=
errors
.
New
(
"peer's unknown or unhealthy"
)
errBadPeer
=
errors
.
New
(
"action from bad peer ignored"
)
errBadPeer
=
errors
.
New
(
"action from bad peer ignored"
)
errNoPeers
=
errors
.
New
(
"no peers to keep download active"
)
errNoPeers
=
errors
.
New
(
"no peers to keep download active"
)
e
rrPendingQueue
=
errors
.
New
(
"pending items in queue"
)
E
rrPendingQueue
=
errors
.
New
(
"pending items in queue"
)
ErrTimeout
=
errors
.
New
(
"timeout"
)
ErrTimeout
=
errors
.
New
(
"timeout"
)
errEmptyHashSet
=
errors
.
New
(
"empty hash set by peer"
)
errEmptyHashSet
=
errors
.
New
(
"empty hash set by peer"
)
errPeersUnavailable
=
errors
.
New
(
"no peers available or all peers tried for block download process"
)
errPeersUnavailable
=
errors
.
New
(
"no peers available or all peers tried for block download process"
)
...
@@ -129,7 +129,7 @@ func (d *Downloader) Synchronise(id string, hash common.Hash) error {
...
@@ -129,7 +129,7 @@ func (d *Downloader) Synchronise(id string, hash common.Hash) error {
// Abort if the queue still contains some leftover data
// Abort if the queue still contains some leftover data
if
_
,
cached
:=
d
.
queue
.
Size
();
cached
>
0
&&
d
.
queue
.
GetHeadBlock
()
!=
nil
{
if
_
,
cached
:=
d
.
queue
.
Size
();
cached
>
0
&&
d
.
queue
.
GetHeadBlock
()
!=
nil
{
return
e
rrPendingQueue
return
E
rrPendingQueue
}
}
// Reset the queue and peer set to clean any internal leftover state
// Reset the queue and peer set to clean any internal leftover state
d
.
queue
.
Reset
()
d
.
queue
.
Reset
()
...
...
eth/sync.go
View file @
0bedf1c3
...
@@ -98,7 +98,8 @@ func (pm *ProtocolManager) synchronise(peer *peer) {
...
@@ -98,7 +98,8 @@ func (pm *ProtocolManager) synchronise(peer *peer) {
case
downloader
.
ErrTimeout
:
case
downloader
.
ErrTimeout
:
glog
.
V
(
logger
.
Debug
)
.
Infof
(
"Removing peer %v due to sync timeout"
,
peer
.
id
)
glog
.
V
(
logger
.
Debug
)
.
Infof
(
"Removing peer %v due to sync timeout"
,
peer
.
id
)
pm
.
removePeer
(
peer
)
pm
.
removePeer
(
peer
)
case
downloader
.
ErrPendingQueue
:
glog
.
V
(
logger
.
Debug
)
.
Infoln
(
"Synchronisation aborted:"
,
err
)
default
:
default
:
glog
.
V
(
logger
.
Warn
)
.
Infof
(
"Synchronisation failed: %v"
,
err
)
glog
.
V
(
logger
.
Warn
)
.
Infof
(
"Synchronisation failed: %v"
,
err
)
}
}
...
...
miner/agent.go
View file @
0bedf1c3
...
@@ -10,7 +10,7 @@ import (
...
@@ -10,7 +10,7 @@ import (
"github.com/ethereum/go-ethereum/pow"
"github.com/ethereum/go-ethereum/pow"
)
)
type
Cpu
Miner
struct
{
type
Cpu
Agent
struct
{
chMu
sync
.
Mutex
chMu
sync
.
Mutex
c
chan
*
types
.
Block
c
chan
*
types
.
Block
quit
chan
struct
{}
quit
chan
struct
{}
...
@@ -21,8 +21,8 @@ type CpuMiner struct {
...
@@ -21,8 +21,8 @@ type CpuMiner struct {
pow
pow
.
PoW
pow
pow
.
PoW
}
}
func
NewCpu
Miner
(
index
int
,
pow
pow
.
PoW
)
*
CpuMiner
{
func
NewCpu
Agent
(
index
int
,
pow
pow
.
PoW
)
*
CpuAgent
{
miner
:=
&
Cpu
Miner
{
miner
:=
&
Cpu
Agent
{
pow
:
pow
,
pow
:
pow
,
index
:
index
,
index
:
index
,
}
}
...
@@ -30,16 +30,16 @@ func NewCpuMiner(index int, pow pow.PoW) *CpuMiner {
...
@@ -30,16 +30,16 @@ func NewCpuMiner(index int, pow pow.PoW) *CpuMiner {
return
miner
return
miner
}
}
func
(
self
*
Cpu
Miner
)
Work
()
chan
<-
*
types
.
Block
{
return
self
.
c
}
func
(
self
*
Cpu
Agent
)
Work
()
chan
<-
*
types
.
Block
{
return
self
.
c
}
func
(
self
*
Cpu
Miner
)
Pow
()
pow
.
PoW
{
return
self
.
pow
}
func
(
self
*
Cpu
Agent
)
Pow
()
pow
.
PoW
{
return
self
.
pow
}
func
(
self
*
Cpu
Miner
)
SetReturnCh
(
ch
chan
<-
*
types
.
Block
)
{
self
.
returnCh
=
ch
}
func
(
self
*
Cpu
Agent
)
SetReturnCh
(
ch
chan
<-
*
types
.
Block
)
{
self
.
returnCh
=
ch
}
func
(
self
*
Cpu
Miner
)
Stop
()
{
func
(
self
*
Cpu
Agent
)
Stop
()
{
close
(
self
.
quit
)
close
(
self
.
quit
)
close
(
self
.
quitCurrentOp
)
close
(
self
.
quitCurrentOp
)
}
}
func
(
self
*
Cpu
Miner
)
Start
()
{
func
(
self
*
Cpu
Agent
)
Start
()
{
self
.
quit
=
make
(
chan
struct
{})
self
.
quit
=
make
(
chan
struct
{})
self
.
quitCurrentOp
=
make
(
chan
struct
{},
1
)
self
.
quitCurrentOp
=
make
(
chan
struct
{},
1
)
self
.
c
=
make
(
chan
*
types
.
Block
,
1
)
self
.
c
=
make
(
chan
*
types
.
Block
,
1
)
...
@@ -47,7 +47,7 @@ func (self *CpuMiner) Start() {
...
@@ -47,7 +47,7 @@ func (self *CpuMiner) Start() {
go
self
.
update
()
go
self
.
update
()
}
}
func
(
self
*
Cpu
Miner
)
update
()
{
func
(
self
*
Cpu
Agent
)
update
()
{
out
:
out
:
for
{
for
{
select
{
select
{
...
@@ -76,7 +76,7 @@ done:
...
@@ -76,7 +76,7 @@ done:
}
}
}
}
func
(
self
*
Cpu
Miner
)
mine
(
block
*
types
.
Block
)
{
func
(
self
*
Cpu
Agent
)
mine
(
block
*
types
.
Block
)
{
glog
.
V
(
logger
.
Debug
)
.
Infof
(
"(re)started agent[%d]. mining...
\n
"
,
self
.
index
)
glog
.
V
(
logger
.
Debug
)
.
Infof
(
"(re)started agent[%d]. mining...
\n
"
,
self
.
index
)
// Reset the channel
// Reset the channel
...
@@ -95,6 +95,6 @@ func (self *CpuMiner) mine(block *types.Block) {
...
@@ -95,6 +95,6 @@ func (self *CpuMiner) mine(block *types.Block) {
}
}
}
}
func
(
self
*
Cpu
Miner
)
GetHashRate
()
int64
{
func
(
self
*
Cpu
Agent
)
GetHashRate
()
int64
{
return
self
.
pow
.
GetHashrate
()
return
self
.
pow
.
GetHashrate
()
}
}
miner/miner.go
View file @
0bedf1c3
...
@@ -23,16 +23,8 @@ type Miner struct {
...
@@ -23,16 +23,8 @@ type Miner struct {
pow
pow
.
PoW
pow
pow
.
PoW
}
}
func
New
(
eth
core
.
Backend
,
pow
pow
.
PoW
,
minerThreads
int
)
*
Miner
{
func
New
(
eth
core
.
Backend
,
pow
pow
.
PoW
)
*
Miner
{
// note: minerThreads is currently ignored because
return
&
Miner
{
eth
:
eth
,
pow
:
pow
,
worker
:
newWorker
(
common
.
Address
{},
eth
)}
// ethash is not thread safe.
miner
:=
&
Miner
{
eth
:
eth
,
pow
:
pow
,
worker
:
newWorker
(
common
.
Address
{},
eth
)}
for
i
:=
0
;
i
<
minerThreads
;
i
++
{
miner
.
worker
.
register
(
NewCpuMiner
(
i
,
pow
))
}
miner
.
threads
=
minerThreads
return
miner
}
}
func
(
self
*
Miner
)
Mining
()
bool
{
func
(
self
*
Miner
)
Mining
()
bool
{
...
@@ -48,15 +40,27 @@ func (m *Miner) SetGasPrice(price *big.Int) {
...
@@ -48,15 +40,27 @@ func (m *Miner) SetGasPrice(price *big.Int) {
m
.
worker
.
gasPrice
=
price
m
.
worker
.
gasPrice
=
price
}
}
func
(
self
*
Miner
)
Start
(
coinbase
common
.
Address
)
{
func
(
self
*
Miner
)
Start
(
coinbase
common
.
Address
,
threads
int
)
{
glog
.
V
(
logger
.
Info
)
.
Infoln
(
"Starting mining operation"
)
self
.
mining
=
true
self
.
mining
=
true
for
i
:=
0
;
i
<
threads
;
i
++
{
self
.
worker
.
register
(
NewCpuAgent
(
i
,
self
.
pow
))
}
self
.
threads
=
threads
glog
.
V
(
logger
.
Info
)
.
Infof
(
"Starting mining operation (CPU=%d TOT=%d)
\n
"
,
threads
,
len
(
self
.
worker
.
agents
))
self
.
worker
.
coinbase
=
coinbase
self
.
worker
.
coinbase
=
coinbase
self
.
worker
.
start
()
self
.
worker
.
start
()
self
.
worker
.
commitNewWork
()
self
.
worker
.
commitNewWork
()
}
}
func
(
self
*
Miner
)
Stop
()
{
self
.
worker
.
stop
()
self
.
mining
=
false
}
func
(
self
*
Miner
)
Register
(
agent
Agent
)
{
func
(
self
*
Miner
)
Register
(
agent
Agent
)
{
if
self
.
mining
{
if
self
.
mining
{
agent
.
Start
()
agent
.
Start
()
...
@@ -65,11 +69,6 @@ func (self *Miner) Register(agent Agent) {
...
@@ -65,11 +69,6 @@ func (self *Miner) Register(agent Agent) {
self
.
worker
.
register
(
agent
)
self
.
worker
.
register
(
agent
)
}
}
func
(
self
*
Miner
)
Stop
()
{
self
.
mining
=
false
self
.
worker
.
stop
()
}
func
(
self
*
Miner
)
HashRate
()
int64
{
func
(
self
*
Miner
)
HashRate
()
int64
{
return
self
.
worker
.
HashRate
()
return
self
.
worker
.
HashRate
()
}
}
...
...
miner/worker.go
View file @
0bedf1c3
...
@@ -141,7 +141,6 @@ func (self *worker) start() {
...
@@ -141,7 +141,6 @@ func (self *worker) start() {
for
_
,
agent
:=
range
self
.
agents
{
for
_
,
agent
:=
range
self
.
agents
{
agent
.
Start
()
agent
.
Start
()
}
}
}
}
func
(
self
*
worker
)
stop
()
{
func
(
self
*
worker
)
stop
()
{
...
@@ -149,10 +148,16 @@ func (self *worker) stop() {
...
@@ -149,10 +148,16 @@ func (self *worker) stop() {
defer
self
.
mu
.
Unlock
()
defer
self
.
mu
.
Unlock
()
if
atomic
.
LoadInt32
(
&
self
.
mining
)
==
1
{
if
atomic
.
LoadInt32
(
&
self
.
mining
)
==
1
{
var
keep
[]
Agent
// stop all agents
// stop all agents
for
_
,
agent
:=
range
self
.
agents
{
for
_
,
agent
:=
range
self
.
agents
{
agent
.
Stop
()
agent
.
Stop
()
// keep all that's not a cpu agent
if
_
,
ok
:=
agent
.
(
*
CpuAgent
);
!
ok
{
keep
=
append
(
keep
,
agent
)
}
}
}
self
.
agents
=
keep
}
}
atomic
.
StoreInt32
(
&
self
.
mining
,
0
)
atomic
.
StoreInt32
(
&
self
.
mining
,
0
)
...
...
rpc/api.go
View file @
0bedf1c3
...
@@ -391,7 +391,7 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
...
@@ -391,7 +391,7 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
}
}
*
reply
=
NewLogsRes
(
api
.
xeth
()
.
AllLogs
(
args
.
Earliest
,
args
.
Latest
,
args
.
Skip
,
args
.
Max
,
args
.
Address
,
args
.
Topics
))
*
reply
=
NewLogsRes
(
api
.
xeth
()
.
AllLogs
(
args
.
Earliest
,
args
.
Latest
,
args
.
Skip
,
args
.
Max
,
args
.
Address
,
args
.
Topics
))
case
"eth_getWork"
:
case
"eth_getWork"
:
api
.
xeth
()
.
SetMining
(
true
)
api
.
xeth
()
.
SetMining
(
true
,
0
)
*
reply
=
api
.
xeth
()
.
RemoteMining
()
.
GetWork
()
*
reply
=
api
.
xeth
()
.
RemoteMining
()
.
GetWork
()
case
"eth_submitWork"
:
case
"eth_submitWork"
:
args
:=
new
(
SubmitWorkArgs
)
args
:=
new
(
SubmitWorkArgs
)
...
...
xeth/xeth.go
View file @
0bedf1c3
...
@@ -425,10 +425,10 @@ func (self *XEth) ClientVersion() string {
...
@@ -425,10 +425,10 @@ func (self *XEth) ClientVersion() string {
return
self
.
backend
.
ClientVersion
()
return
self
.
backend
.
ClientVersion
()
}
}
func
(
self
*
XEth
)
SetMining
(
shouldmine
bool
)
bool
{
func
(
self
*
XEth
)
SetMining
(
shouldmine
bool
,
threads
int
)
bool
{
ismining
:=
self
.
backend
.
IsMining
()
ismining
:=
self
.
backend
.
IsMining
()
if
shouldmine
&&
!
ismining
{
if
shouldmine
&&
!
ismining
{
err
:=
self
.
backend
.
StartMining
()
err
:=
self
.
backend
.
StartMining
(
threads
)
return
err
==
nil
return
err
==
nil
}
}
if
ismining
&&
!
shouldmine
{
if
ismining
&&
!
shouldmine
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment