Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
M
mybee
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
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
vicotor
mybee
Commits
6f74a2f4
Unverified
Commit
6f74a2f4
authored
Jul 05, 2021
by
Anatolie Lupacescu
Committed by
GitHub
Jul 05, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: default network config (#2232)
parent
d97c1a20
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
109 additions
and
104 deletions
+109
-104
cmd.go
cmd/bee/cmd/cmd.go
+1
-1
start.go
cmd/bee/cmd/start.go
+39
-36
chain.go
pkg/config/chain.go
+55
-0
chain.go
pkg/node/chain.go
+6
-3
node.go
pkg/node/node.go
+8
-1
listener.go
pkg/postage/listener/listener.go
+0
-21
factory.go
pkg/settlement/swap/chequebook/factory.go
+0
-24
priceoracle.go
pkg/settlement/swap/priceoracle/priceoracle.go
+0
-18
No files found.
cmd/bee/cmd/cmd.go
View file @
6f74a2f4
...
@@ -210,7 +210,7 @@ func (c *command) setAllFlags(cmd *cobra.Command) {
...
@@ -210,7 +210,7 @@ func (c *command) setAllFlags(cmd *cobra.Command) {
cmd
.
Flags
()
.
String
(
optionNameNATAddr
,
""
,
"NAT exposed address"
)
cmd
.
Flags
()
.
String
(
optionNameNATAddr
,
""
,
"NAT exposed address"
)
cmd
.
Flags
()
.
Bool
(
optionNameP2PWSEnable
,
false
,
"enable P2P WebSocket transport"
)
cmd
.
Flags
()
.
Bool
(
optionNameP2PWSEnable
,
false
,
"enable P2P WebSocket transport"
)
cmd
.
Flags
()
.
Bool
(
optionNameP2PQUICEnable
,
false
,
"enable P2P QUIC transport"
)
cmd
.
Flags
()
.
Bool
(
optionNameP2PQUICEnable
,
false
,
"enable P2P QUIC transport"
)
cmd
.
Flags
()
.
StringSlice
(
optionNameBootnodes
,
[]
string
{},
"initial nodes to connect to"
)
cmd
.
Flags
()
.
StringSlice
(
optionNameBootnodes
,
[]
string
{
"/dnsaddr/testnet.ethswarm.org"
},
"initial nodes to connect to"
)
cmd
.
Flags
()
.
Bool
(
optionNameDebugAPIEnable
,
false
,
"enable debug HTTP API"
)
cmd
.
Flags
()
.
Bool
(
optionNameDebugAPIEnable
,
false
,
"enable debug HTTP API"
)
cmd
.
Flags
()
.
String
(
optionNameDebugAPIAddr
,
":1635"
,
"debug HTTP API listen address"
)
cmd
.
Flags
()
.
String
(
optionNameDebugAPIAddr
,
":1635"
,
"debug HTTP API listen address"
)
cmd
.
Flags
()
.
Uint64
(
optionNameNetworkID
,
10
,
"ID of the Swarm network"
)
cmd
.
Flags
()
.
Uint64
(
optionNameNetworkID
,
10
,
"ID of the Swarm network"
)
...
...
cmd/bee/cmd/start.go
View file @
6f74a2f4
...
@@ -128,18 +128,28 @@ inability to use, or your interaction with other nodes or the software.`)
...
@@ -128,18 +128,28 @@ inability to use, or your interaction with other nodes or the software.`)
}
}
mainnet
:=
c
.
config
.
GetBool
(
optionNameMainNet
)
mainnet
:=
c
.
config
.
GetBool
(
optionNameMainNet
)
networkID
:=
c
.
config
.
GetUint64
(
optionNameNetworkID
)
networkID
:=
c
.
config
.
GetUint64
(
optionNameNetworkID
)
networkID
,
err
=
parseNetworks
(
mainnet
,
networkID
)
if
err
!=
nil
{
if
mainnet
{
return
err
userHasSetNetworkID
:=
c
.
config
.
IsSet
(
optionNameNetworkID
)
if
userHasSetNetworkID
&&
networkID
!=
1
{
return
errors
.
New
(
"provided network ID does not match mainnet"
)
}
networkID
=
1
}
}
bootnodes
:=
c
.
config
.
GetStringSlice
(
optionNameBootnodes
)
bootnodes
:=
c
.
config
.
GetStringSlice
(
optionNameBootnodes
)
bootnodes
=
parseBootnodes
(
logger
,
mainnet
,
networkID
,
bootnodes
)
blockTime
:=
c
.
config
.
GetUint64
(
optionNameBlockTime
)
blockTime
:=
c
.
config
.
GetUint64
(
optionNameBlockTime
)
blockTime
=
parseBlockTime
(
mainnet
,
blockTime
)
networkConfig
:=
getConfigByNetworkID
(
networkID
,
blockTime
)
if
c
.
config
.
IsSet
(
optionNameBootnodes
)
{
networkConfig
.
bootNodes
=
bootnodes
}
if
c
.
config
.
IsSet
(
optionNameBlockTime
)
&&
blockTime
!=
0
{
networkConfig
.
blockTime
=
blockTime
}
b
,
err
:=
node
.
NewBee
(
c
.
config
.
GetString
(
optionNameP2PAddr
),
signerConfig
.
publicKey
,
signerConfig
.
signer
,
networkID
,
logger
,
signerConfig
.
libp2pPrivateKey
,
signerConfig
.
pssPrivateKey
,
&
node
.
Options
{
b
,
err
:=
node
.
NewBee
(
c
.
config
.
GetString
(
optionNameP2PAddr
),
signerConfig
.
publicKey
,
signerConfig
.
signer
,
networkID
,
logger
,
signerConfig
.
libp2pPrivateKey
,
signerConfig
.
pssPrivateKey
,
&
node
.
Options
{
DataDir
:
c
.
config
.
GetString
(
optionNameDataDir
),
DataDir
:
c
.
config
.
GetString
(
optionNameDataDir
),
...
@@ -155,7 +165,7 @@ inability to use, or your interaction with other nodes or the software.`)
...
@@ -155,7 +165,7 @@ inability to use, or your interaction with other nodes or the software.`)
EnableWS
:
c
.
config
.
GetBool
(
optionNameP2PWSEnable
),
EnableWS
:
c
.
config
.
GetBool
(
optionNameP2PWSEnable
),
EnableQUIC
:
c
.
config
.
GetBool
(
optionNameP2PQUICEnable
),
EnableQUIC
:
c
.
config
.
GetBool
(
optionNameP2PQUICEnable
),
WelcomeMessage
:
c
.
config
.
GetString
(
optionWelcomeMessage
),
WelcomeMessage
:
c
.
config
.
GetString
(
optionWelcomeMessage
),
Bootnodes
:
bootn
odes
,
Bootnodes
:
networkConfig
.
bootN
odes
,
CORSAllowedOrigins
:
c
.
config
.
GetStringSlice
(
optionCORSAllowedOrigins
),
CORSAllowedOrigins
:
c
.
config
.
GetStringSlice
(
optionCORSAllowedOrigins
),
Standalone
:
c
.
config
.
GetBool
(
optionNameStandalone
),
Standalone
:
c
.
config
.
GetBool
(
optionNameStandalone
),
TracingEnabled
:
c
.
config
.
GetBool
(
optionNameTracingEnabled
),
TracingEnabled
:
c
.
config
.
GetBool
(
optionNameTracingEnabled
),
...
@@ -179,9 +189,10 @@ inability to use, or your interaction with other nodes or the software.`)
...
@@ -179,9 +189,10 @@ inability to use, or your interaction with other nodes or the software.`)
BlockHash
:
c
.
config
.
GetString
(
optionNameBlockHash
),
BlockHash
:
c
.
config
.
GetString
(
optionNameBlockHash
),
PostageContractAddress
:
c
.
config
.
GetString
(
optionNamePostageContractAddress
),
PostageContractAddress
:
c
.
config
.
GetString
(
optionNamePostageContractAddress
),
PriceOracleAddress
:
c
.
config
.
GetString
(
optionNamePriceOracleAddress
),
PriceOracleAddress
:
c
.
config
.
GetString
(
optionNamePriceOracleAddress
),
BlockTime
:
blockTime
,
BlockTime
:
networkConfig
.
blockTime
,
DeployGasPrice
:
c
.
config
.
GetString
(
optionNameSwapDeploymentGasPrice
),
DeployGasPrice
:
c
.
config
.
GetString
(
optionNameSwapDeploymentGasPrice
),
WarmupTime
:
c
.
config
.
GetDuration
(
optionWarmUpTime
),
WarmupTime
:
c
.
config
.
GetDuration
(
optionWarmUpTime
),
ChainID
:
networkConfig
.
chainID
,
})
})
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
...
@@ -420,36 +431,28 @@ func (c *command) configureSigner(cmd *cobra.Command, logger logging.Logger) (co
...
@@ -420,36 +431,28 @@ func (c *command) configureSigner(cmd *cobra.Command, logger logging.Logger) (co
},
nil
},
nil
}
}
func
parseNetworks
(
main
bool
,
networkID
uint64
)
(
uint64
,
error
)
{
type
networkConfig
struct
{
if
main
&&
networkID
!=
1
{
bootNodes
[]
string
return
0
,
errors
.
New
(
"provided network ID does not match mainnet"
)
blockTime
uint64
}
chainID
int64
return
networkID
,
nil
}
}
func
parseBootnodes
(
log
logging
.
Logger
,
main
bool
,
networkID
uint64
,
bootnodes
[]
string
)
[]
string
{
func
getConfigByNetworkID
(
networkID
uint64
,
defaultBlockTime
uint64
)
*
networkConfig
{
if
len
(
bootnodes
)
>
0
{
var
config
=
networkConfig
{
return
bootnodes
// use provided values
blockTime
:
uint64
(
time
.
Duration
(
defaultBlockTime
)
*
time
.
Second
),
}
if
main
{
return
[]
string
{
"/dnsaddr/mainnet.ethswarm.org"
}
}
if
networkID
==
10
{
return
[]
string
{
"/dnsaddr/testnet.ethswarm.org"
}
}
}
switch
networkID
{
log
.
Warning
(
"no bootnodes defined for network ID"
,
networkID
)
case
1
:
config
.
bootNodes
=
[]
string
{
"/dnsaddr/mainnet.ethswarm.org"
}
return
bootnodes
config
.
blockTime
=
uint64
(
5
*
time
.
Second
)
}
config
.
chainID
=
100
case
5
:
//staging
func
parseBlockTime
(
main
bool
,
blockTime
uint64
)
uint64
{
config
.
chainID
=
5
if
main
{
case
10
:
//test
return
uint64
(
5
*
time
.
Second
)
config
.
chainID
=
5
default
:
//will use the value provided by the chain
config
.
chainID
=
-
1
}
}
return
blockTime
return
&
config
}
}
pkg/config/chain.go
0 → 100644
View file @
6f74a2f4
package
config
import
(
"github.com/ethereum/go-ethereum/common"
)
var
(
// chain ID
goerliChainID
=
int64
(
5
)
xdaiChainID
=
int64
(
100
)
// start block
goerliStartBlock
=
uint64
(
4933174
)
xdaiStartBlock
=
uint64
(
16515648
)
// factory address
goerliContractAddress
=
common
.
HexToAddress
(
"0x0c9de531dcb38b758fe8a2c163444a5e54ee0db2"
)
xdaiContractAddress
=
common
.
HexToAddress
(
"0x0FDc5429C50e2a39066D8A94F3e2D2476fcc3b85"
)
goerliFactoryAddress
=
common
.
HexToAddress
(
"0x73c412512E1cA0be3b89b77aB3466dA6A1B9d273"
)
xdaiFactoryAddress
=
common
.
HexToAddress
(
"0xc2d5a532cf69aa9a1378737d8ccdef884b6e7420"
)
goerliLegacyFactoryAddress
=
common
.
HexToAddress
(
"0xf0277caffea72734853b834afc9892461ea18474"
)
// postage stamp
goerliPostageStampContractAddress
=
common
.
HexToAddress
(
"0x621e455C4a139f5C4e4A8122Ce55Dc21630769E4"
)
xdaiPostageStampContractAddress
=
common
.
HexToAddress
(
"0x6a1a21eca3ab28be85c7ba22b2d6eae5907c900e"
)
)
type
ChainConfig
struct
{
StartBlock
uint64
LegacyFactories
[]
common
.
Address
PostageStamp
common
.
Address
CurrentFactory
common
.
Address
PriceOracleAddress
common
.
Address
}
func
GetChainConfig
(
chainID
int64
)
(
*
ChainConfig
,
bool
)
{
var
cfg
ChainConfig
switch
chainID
{
case
goerliChainID
:
cfg
.
PostageStamp
=
goerliPostageStampContractAddress
cfg
.
StartBlock
=
goerliStartBlock
cfg
.
CurrentFactory
=
goerliFactoryAddress
cfg
.
LegacyFactories
=
[]
common
.
Address
{
goerliLegacyFactoryAddress
,
}
cfg
.
PriceOracleAddress
=
goerliContractAddress
return
&
cfg
,
true
case
xdaiChainID
:
cfg
.
PostageStamp
=
xdaiPostageStampContractAddress
cfg
.
StartBlock
=
xdaiStartBlock
cfg
.
CurrentFactory
=
xdaiFactoryAddress
cfg
.
LegacyFactories
=
[]
common
.
Address
{}
cfg
.
PriceOracleAddress
=
xdaiContractAddress
return
&
cfg
,
true
default
:
return
&
cfg
,
false
}
}
pkg/node/chain.go
View file @
6f74a2f4
...
@@ -15,6 +15,7 @@ import (
...
@@ -15,6 +15,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethersphere/bee/pkg/config"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/p2p/libp2p"
"github.com/ethersphere/bee/pkg/p2p/libp2p"
...
@@ -82,7 +83,9 @@ func InitChequebookFactory(
...
@@ -82,7 +83,9 @@ func InitChequebookFactory(
var
currentFactory
common
.
Address
var
currentFactory
common
.
Address
var
legacyFactories
[]
common
.
Address
var
legacyFactories
[]
common
.
Address
foundFactory
,
foundLegacyFactories
,
found
:=
chequebook
.
DiscoverFactoryAddress
(
chainID
)
chainCfg
,
found
:=
config
.
GetChainConfig
(
chainID
)
foundFactory
,
foundLegacyFactories
:=
chainCfg
.
CurrentFactory
,
chainCfg
.
LegacyFactories
if
factoryAddress
==
""
{
if
factoryAddress
==
""
{
if
!
found
{
if
!
found
{
return
nil
,
fmt
.
Errorf
(
"no known factory address for this network (chain id: %d)"
,
chainID
)
return
nil
,
fmt
.
Errorf
(
"no known factory address for this network (chain id: %d)"
,
chainID
)
...
@@ -211,8 +214,8 @@ func InitSwap(
...
@@ -211,8 +214,8 @@ func InitSwap(
var
currentPriceOracleAddress
common
.
Address
var
currentPriceOracleAddress
common
.
Address
if
priceOracleAddress
==
""
{
if
priceOracleAddress
==
""
{
var
found
bool
chainCfg
,
found
:=
config
.
GetChainConfig
(
chainID
)
currentPriceOracleAddress
,
found
=
priceoracle
.
DiscoverPriceOracleAddress
(
chainID
)
currentPriceOracleAddress
=
chainCfg
.
PriceOracleAddress
if
!
found
{
if
!
found
{
return
nil
,
nil
,
errors
.
New
(
"no known price oracle address for this network"
)
return
nil
,
nil
,
errors
.
New
(
"no known price oracle address for this network"
)
}
}
...
...
pkg/node/node.go
View file @
6f74a2f4
...
@@ -29,6 +29,7 @@ import (
...
@@ -29,6 +29,7 @@ import (
"github.com/ethersphere/bee/pkg/accounting"
"github.com/ethersphere/bee/pkg/accounting"
"github.com/ethersphere/bee/pkg/addressbook"
"github.com/ethersphere/bee/pkg/addressbook"
"github.com/ethersphere/bee/pkg/api"
"github.com/ethersphere/bee/pkg/api"
"github.com/ethersphere/bee/pkg/config"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/debugapi"
"github.com/ethersphere/bee/pkg/debugapi"
"github.com/ethersphere/bee/pkg/feeds/factory"
"github.com/ethersphere/bee/pkg/feeds/factory"
...
@@ -151,6 +152,7 @@ type Options struct {
...
@@ -151,6 +152,7 @@ type Options struct {
BlockTime
uint64
BlockTime
uint64
DeployGasPrice
string
DeployGasPrice
string
WarmupTime
time
.
Duration
WarmupTime
time
.
Duration
ChainID
int64
}
}
const
(
const
(
...
@@ -225,6 +227,10 @@ func NewBee(addr string, publicKey *ecdsa.PublicKey, signer crypto.Signer, netwo
...
@@ -225,6 +227,10 @@ func NewBee(addr string, publicKey *ecdsa.PublicKey, signer crypto.Signer, netwo
b
.
ethClientCloser
=
swapBackend
.
Close
b
.
ethClientCloser
=
swapBackend
.
Close
b
.
transactionCloser
=
tracerCloser
b
.
transactionCloser
=
tracerCloser
b
.
transactionMonitorCloser
=
transactionMonitor
b
.
transactionMonitorCloser
=
transactionMonitor
if
o
.
ChainID
!=
-
1
&&
o
.
ChainID
!=
chainID
{
return
nil
,
fmt
.
Errorf
(
"connected to wrong ethereum network: got chainID %d, want %d"
,
chainID
,
o
.
ChainID
)
}
}
}
var
debugAPIService
*
debugapi
.
Service
var
debugAPIService
*
debugapi
.
Service
...
@@ -417,7 +423,8 @@ func NewBee(addr string, publicKey *ecdsa.PublicKey, signer crypto.Signer, netwo
...
@@ -417,7 +423,8 @@ func NewBee(addr string, publicKey *ecdsa.PublicKey, signer crypto.Signer, netwo
var
postageSyncStart
uint64
=
0
var
postageSyncStart
uint64
=
0
if
!
o
.
Standalone
{
if
!
o
.
Standalone
{
postageContractAddress
,
startBlock
,
found
:=
listener
.
DiscoverAddresses
(
chainID
)
chainCfg
,
found
:=
config
.
GetChainConfig
(
chainID
)
postageContractAddress
,
startBlock
:=
chainCfg
.
PostageStamp
,
chainCfg
.
StartBlock
if
o
.
PostageContractAddress
!=
""
{
if
o
.
PostageContractAddress
!=
""
{
if
!
common
.
IsHexAddress
(
o
.
PostageContractAddress
)
{
if
!
common
.
IsHexAddress
(
o
.
PostageContractAddress
)
{
return
nil
,
errors
.
New
(
"malformed postage stamp address"
)
return
nil
,
errors
.
New
(
"malformed postage stamp address"
)
...
...
pkg/postage/listener/listener.go
View file @
6f74a2f4
...
@@ -327,27 +327,6 @@ type priceUpdateEvent struct {
...
@@ -327,27 +327,6 @@ type priceUpdateEvent struct {
Price
*
big
.
Int
Price
*
big
.
Int
}
}
var
(
GoerliChainID
=
int64
(
5
)
GoerliPostageStampContractAddress
=
common
.
HexToAddress
(
"0x621e455C4a139f5C4e4A8122Ce55Dc21630769E4"
)
GoerliStartBlock
=
uint64
(
4933174
)
XDaiChainID
=
int64
(
100
)
XDaiPostageStampContractAddress
=
common
.
HexToAddress
(
"0x6a1a21eca3ab28be85c7ba22b2d6eae5907c900e"
)
XDaiStartBlock
=
uint64
(
16515648
)
)
// DiscoverAddresses returns the canonical contracts for this chainID
func
DiscoverAddresses
(
chainID
int64
)
(
postageStamp
common
.
Address
,
startBlock
uint64
,
found
bool
)
{
switch
chainID
{
case
GoerliChainID
:
return
GoerliPostageStampContractAddress
,
GoerliStartBlock
,
true
case
XDaiChainID
:
return
XDaiPostageStampContractAddress
,
XDaiStartBlock
,
true
default
:
return
common
.
Address
{},
0
,
false
}
}
func
totalTimeMetric
(
metric
prometheus
.
Counter
,
start
time
.
Time
)
{
func
totalTimeMetric
(
metric
prometheus
.
Counter
,
start
time
.
Time
)
{
totalTime
:=
time
.
Since
(
start
)
totalTime
:=
time
.
Since
(
start
)
metric
.
Add
(
float64
(
totalTime
))
metric
.
Add
(
float64
(
totalTime
))
...
...
pkg/settlement/swap/chequebook/factory.go
View file @
6f74a2f4
...
@@ -227,27 +227,3 @@ func (c *factory) ERC20Address(ctx context.Context) (common.Address, error) {
...
@@ -227,27 +227,3 @@ func (c *factory) ERC20Address(ctx context.Context) (common.Address, error) {
}
}
return
*
erc20Address
,
nil
return
*
erc20Address
,
nil
}
}
var
(
GoerliChainID
=
int64
(
5
)
GoerliFactoryAddress
=
common
.
HexToAddress
(
"0x73c412512E1cA0be3b89b77aB3466dA6A1B9d273"
)
GoerliLegacyFactoryAddress
=
common
.
HexToAddress
(
"0xf0277caffea72734853b834afc9892461ea18474"
)
XDaiChainID
=
int64
(
100
)
XDaiFactoryAddress
=
common
.
HexToAddress
(
"0xc2d5a532cf69aa9a1378737d8ccdef884b6e7420"
)
)
// DiscoverFactoryAddress returns the canonical factory for this chainID
func
DiscoverFactoryAddress
(
chainID
int64
)
(
currentFactory
common
.
Address
,
legacyFactories
[]
common
.
Address
,
found
bool
)
{
switch
chainID
{
case
GoerliChainID
:
// goerli
return
GoerliFactoryAddress
,
[]
common
.
Address
{
GoerliLegacyFactoryAddress
,
},
true
case
XDaiChainID
:
// xdai
return
XDaiFactoryAddress
,
[]
common
.
Address
{},
true
default
:
return
common
.
Address
{},
nil
,
false
}
}
pkg/settlement/swap/priceoracle/priceoracle.go
View file @
6f74a2f4
...
@@ -145,21 +145,3 @@ func (s *service) Close() error {
...
@@ -145,21 +145,3 @@ func (s *service) Close() error {
close
(
s
.
quitC
)
close
(
s
.
quitC
)
return
nil
return
nil
}
}
var
(
goerliChainID
=
int64
(
5
)
goerliContractAddress
=
common
.
HexToAddress
(
"0x0c9de531dcb38b758fe8a2c163444a5e54ee0db2"
)
xdaiChainID
=
int64
(
100
)
xdaiContractAddress
=
common
.
HexToAddress
(
"0x0FDc5429C50e2a39066D8A94F3e2D2476fcc3b85"
)
)
// DiscoverPriceOracleAddress returns the canonical price oracle for this chainID
func
DiscoverPriceOracleAddress
(
chainID
int64
)
(
priceOracleAddress
common
.
Address
,
found
bool
)
{
switch
chainID
{
case
goerliChainID
:
return
goerliContractAddress
,
true
case
xdaiChainID
:
return
xdaiContractAddress
,
true
}
return
common
.
Address
{},
false
}
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