Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
S
sidecar-client-fabric
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
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
link33
sidecar-client-fabric
Commits
84277dbf
Commit
84277dbf
authored
Mar 12, 2021
by
zhourong
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor(*): adapter biz contract flexibly
parent
26463b82
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
328 additions
and
564 deletions
+328
-564
client.go
client.go
+156
-69
event.go
event.go
+15
-5
contracts.zip
example/contracts.zip
+0
-0
asset_exchange.go
example/contracts/src/broker/asset_exchange.go
+0
-144
broker.go
example/contracts/src/broker/broker.go
+130
-148
data_swapper.go
example/contracts/src/broker/data_swapper.go
+0
-86
transfer.go
example/contracts/src/broker/transfer.go
+0
-95
data_swapper.go
example/contracts/src/data_swapper/data_swapper.go
+4
-5
transfer.go
example/contracts/src/transfer/transfer.go
+6
-5
go.mod
go.mod
+8
-4
go.sum
go.sum
+0
-0
receipt.go
receipt.go
+9
-3
No files found.
client.go
View file @
84277dbf
This diff is collapsed.
Click to expand it.
event.go
View file @
84277dbf
...
...
@@ -18,6 +18,9 @@ type Event struct {
Func
string
`json:"func"`
Args
string
`json:"args"`
Callback
string
`json:"callback"`
Argscb
string
`json:"argscb"`
Rollback
string
`json:"rollback"`
Argsrb
string
`json:"argsrb"`
Proof
[]
byte
`json:"proof"`
Extra
[]
byte
`json:"extra"`
}
...
...
@@ -55,18 +58,25 @@ func (ev *Event) Convert2IBTP(from string, ibtpType pb.IBTP_Type) *pb.IBTP {
}
}
func
(
ev
*
Event
)
encryptPayload
()
([]
byte
,
error
)
{
args
:=
make
([][]
byte
,
0
)
as
:=
strings
.
Split
(
ev
.
A
rgs
,
","
)
func
handleArgs
(
args
string
)
[][]
byte
{
args
Bytes
:=
make
([][]
byte
,
0
)
as
:=
strings
.
Split
(
a
rgs
,
","
)
for
_
,
a
:=
range
as
{
args
=
append
(
arg
s
,
[]
byte
(
a
))
args
Bytes
=
append
(
argsByte
s
,
[]
byte
(
a
))
}
return
argsBytes
}
func
(
ev
*
Event
)
encryptPayload
()
([]
byte
,
error
)
{
content
:=
&
pb
.
Content
{
SrcContractId
:
ev
.
SrcContractID
,
DstContractId
:
ev
.
DstContractID
,
Func
:
ev
.
Func
,
Args
:
args
,
Args
:
handleArgs
(
ev
.
Args
)
,
Callback
:
ev
.
Callback
,
ArgsCb
:
handleArgs
(
ev
.
Argscb
),
Rollback
:
ev
.
Rollback
,
ArgsRb
:
handleArgs
(
ev
.
Argsrb
),
}
data
,
err
:=
content
.
Marshal
()
if
err
!=
nil
{
...
...
example/contracts.zip
View file @
84277dbf
No preview for this file type
example/contracts/src/broker/asset_exchange.go
deleted
100644 → 0
View file @
26463b82
package
main
import
(
"fmt"
"strconv"
"strings"
"github.com/hyperledger/fabric/common/util"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb
"github.com/hyperledger/fabric/protos/peer"
)
func
(
broker
*
Broker
)
interchainAssetExchangeInit
(
stub
shim
.
ChaincodeStubInterface
,
args
[]
string
)
pb
.
Response
{
if
len
(
args
)
!=
11
{
return
errorResponse
(
"incorrect number of arguments, expecting 11"
)
}
sourceChainID
:=
args
[
0
]
sequenceNum
:=
args
[
1
]
targetCID
:=
args
[
2
]
sourceCID
:=
args
[
3
]
assetExchangeId
:=
args
[
4
]
senderOnSrcChain
:=
args
[
5
]
receiverOnSrcChain
:=
args
[
6
]
assetOnSrcChain
:=
args
[
7
]
senderOnDstChain
:=
args
[
8
]
receiverOnDstChain
:=
args
[
9
]
assetOnDstChain
:=
args
[
10
]
if
err
:=
broker
.
checkIndex
(
stub
,
sourceChainID
,
sequenceNum
,
innerMeta
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
if
err
:=
broker
.
markInCounter
(
stub
,
sourceChainID
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
splitedCID
:=
strings
.
Split
(
targetCID
,
delimiter
)
if
len
(
splitedCID
)
!=
2
{
return
errorResponse
(
fmt
.
Sprintf
(
"Target chaincode id %s is not valid"
,
targetCID
))
}
b
:=
util
.
ToChaincodeArgs
(
"interchainAssetExchangeInit"
,
sourceChainID
,
sourceCID
,
assetExchangeId
,
senderOnSrcChain
,
receiverOnSrcChain
,
assetOnSrcChain
,
senderOnDstChain
,
receiverOnDstChain
,
assetOnDstChain
)
response
:=
stub
.
InvokeChaincode
(
splitedCID
[
1
],
b
,
splitedCID
[
0
])
if
response
.
Status
!=
shim
.
OK
{
return
errorResponse
(
fmt
.
Sprintf
(
"invoke chaincode '%s' err: %s"
,
splitedCID
[
1
],
response
.
Message
))
}
// persist execution result
key
:=
broker
.
inMsgKey
(
sourceChainID
,
sequenceNum
)
if
err
:=
stub
.
PutState
(
key
,
response
.
Payload
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
return
successResponse
(
nil
)
}
func
(
broker
*
Broker
)
interchainAssetExchangeRedeem
(
stub
shim
.
ChaincodeStubInterface
,
args
[]
string
)
pb
.
Response
{
return
broker
.
interchainAssetExchangeFinish
(
stub
,
args
,
"1"
)
}
func
(
broker
*
Broker
)
interchainAssetExchangeRefund
(
stub
shim
.
ChaincodeStubInterface
,
args
[]
string
)
pb
.
Response
{
return
broker
.
interchainAssetExchangeFinish
(
stub
,
args
,
"2"
)
}
func
(
broker
*
Broker
)
interchainAssetExchangeFinish
(
stub
shim
.
ChaincodeStubInterface
,
args
[]
string
,
status
string
)
pb
.
Response
{
// check args
if
len
(
args
)
!=
5
{
return
errorResponse
(
"incorrect number of arguments, expecting 5"
)
}
sourceChainID
:=
args
[
0
]
sequenceNum
:=
args
[
1
]
targetCID
:=
args
[
2
]
assetExchangeId
:=
args
[
3
]
signatures
:=
args
[
4
]
if
err
:=
broker
.
checkIndex
(
stub
,
sourceChainID
,
sequenceNum
,
innerMeta
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
if
err
:=
broker
.
markInCounter
(
stub
,
sourceChainID
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
splitedCID
:=
strings
.
Split
(
targetCID
,
delimiter
)
if
len
(
splitedCID
)
!=
2
{
return
errorResponse
(
fmt
.
Sprintf
(
"Target chaincode id %s is not valid"
,
targetCID
))
}
b
:=
util
.
ToChaincodeArgs
(
"interchainAssetExchangeFinish"
,
assetExchangeId
,
status
,
signatures
)
response
:=
stub
.
InvokeChaincode
(
splitedCID
[
1
],
b
,
splitedCID
[
0
])
if
response
.
Status
!=
shim
.
OK
{
return
errorResponse
(
fmt
.
Sprintf
(
"invoke chaincode '%s' err: %s"
,
splitedCID
[
1
],
response
.
Message
))
}
return
successResponse
(
nil
)
}
func
(
broker
*
Broker
)
interchainAssetExchangeConfirm
(
stub
shim
.
ChaincodeStubInterface
,
args
[]
string
)
pb
.
Response
{
// check args
if
len
(
args
)
!=
5
{
return
errorResponse
(
"incorrect number of arguments, expecting 5"
)
}
sourceChainID
:=
args
[
0
]
sequenceNum
:=
args
[
1
]
targetCID
:=
args
[
2
]
assetExchangeId
:=
args
[
3
]
signatures
:=
args
[
4
]
if
err
:=
broker
.
checkIndex
(
stub
,
sourceChainID
,
sequenceNum
,
callbackMeta
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
idx
,
err
:=
strconv
.
ParseUint
(
sequenceNum
,
10
,
64
)
if
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
if
err
:=
broker
.
markCallbackCounter
(
stub
,
sourceChainID
,
idx
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
splitedCID
:=
strings
.
Split
(
targetCID
,
delimiter
)
if
len
(
splitedCID
)
!=
2
{
return
errorResponse
(
fmt
.
Sprintf
(
"Target chaincode id %s is not valid"
,
targetCID
))
}
b
:=
util
.
ToChaincodeArgs
(
"interchainAssetExchangeConfirm"
,
assetExchangeId
,
signatures
)
response
:=
stub
.
InvokeChaincode
(
splitedCID
[
1
],
b
,
splitedCID
[
0
])
if
response
.
Status
!=
shim
.
OK
{
return
errorResponse
(
fmt
.
Sprintf
(
"invoke chaincode '%s' err: %s"
,
splitedCID
[
1
],
response
.
Message
))
}
return
successResponse
(
nil
)
}
example/contracts/src/broker/broker.go
View file @
84277dbf
This diff is collapsed.
Click to expand it.
example/contracts/src/broker/data_swapper.go
deleted
100644 → 0
View file @
26463b82
package
main
import
(
"fmt"
"strconv"
"strings"
"github.com/hyperledger/fabric/common/util"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb
"github.com/hyperledger/fabric/protos/peer"
)
// get interchain account for transfer contract: setData from,index,tid,name_id,amount
func
(
broker
*
Broker
)
interchainSet
(
stub
shim
.
ChaincodeStubInterface
,
args
[]
string
)
pb
.
Response
{
if
len
(
args
)
<
5
{
return
errorResponse
(
"incorrect number of arguments, expecting 5"
)
}
sourceChainID
:=
args
[
0
]
sequenceNum
:=
args
[
1
]
targetCID
:=
args
[
2
]
key
:=
args
[
3
]
data
:=
args
[
4
]
if
err
:=
broker
.
checkIndex
(
stub
,
sourceChainID
,
sequenceNum
,
callbackMeta
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
idx
,
err
:=
strconv
.
ParseUint
(
sequenceNum
,
10
,
64
)
if
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
if
err
:=
broker
.
markCallbackCounter
(
stub
,
sourceChainID
,
idx
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
splitedCID
:=
strings
.
Split
(
targetCID
,
delimiter
)
if
len
(
splitedCID
)
!=
2
{
return
errorResponse
(
fmt
.
Sprintf
(
"Target chaincode id %s is not valid"
,
targetCID
))
}
b
:=
util
.
ToChaincodeArgs
(
"interchainSet"
,
key
,
data
)
response
:=
stub
.
InvokeChaincode
(
splitedCID
[
1
],
b
,
splitedCID
[
0
])
if
response
.
Status
!=
shim
.
OK
{
return
errorResponse
(
fmt
.
Sprintf
(
"invoke chaincode '%s' err: %s"
,
splitedCID
[
1
],
response
.
Message
))
}
return
successResponse
(
nil
)
}
// example for calling get: getData from,index,tid,id
func
(
broker
*
Broker
)
interchainGet
(
stub
shim
.
ChaincodeStubInterface
,
args
[]
string
)
pb
.
Response
{
if
len
(
args
)
<
4
{
return
errorResponse
(
"incorrect number of arguments, expecting 4"
)
}
sourceChainID
:=
args
[
0
]
sequenceNum
:=
args
[
1
]
targetCID
:=
args
[
2
]
key
:=
args
[
3
]
if
err
:=
broker
.
checkIndex
(
stub
,
sourceChainID
,
sequenceNum
,
innerMeta
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
if
err
:=
broker
.
markInCounter
(
stub
,
sourceChainID
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
splitedCID
:=
strings
.
Split
(
targetCID
,
delimiter
)
if
len
(
splitedCID
)
!=
2
{
return
errorResponse
(
fmt
.
Sprintf
(
"Target chaincode id %s is not valid"
,
targetCID
))
}
b
:=
util
.
ToChaincodeArgs
(
"interchainGet"
,
key
)
response
:=
stub
.
InvokeChaincode
(
splitedCID
[
1
],
b
,
splitedCID
[
0
])
if
response
.
Status
!=
shim
.
OK
{
return
errorResponse
(
fmt
.
Sprintf
(
"invoke chaincode '%s' err: %s"
,
splitedCID
[
1
],
response
.
Message
))
}
inKey
:=
broker
.
inMsgKey
(
sourceChainID
,
sequenceNum
)
if
err
:=
stub
.
PutState
(
inKey
,
response
.
Payload
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
return
successResponse
(
response
.
Payload
)
}
example/contracts/src/broker/transfer.go
deleted
100644 → 0
View file @
26463b82
package
main
import
(
"fmt"
"strconv"
"strings"
"github.com/hyperledger/fabric/common/util"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb
"github.com/hyperledger/fabric/protos/peer"
)
// recharge for transfer contract: charge from,index,tid,name_id,amount
func
(
broker
*
Broker
)
interchainCharge
(
stub
shim
.
ChaincodeStubInterface
,
args
[]
string
)
pb
.
Response
{
if
len
(
args
)
<
6
{
return
errorResponse
(
"incorrect number of arguments, expecting 6"
)
}
sourceChainID
:=
args
[
0
]
sequenceNum
:=
args
[
1
]
targetCID
:=
args
[
2
]
sender
:=
args
[
3
]
receiver
:=
args
[
4
]
amount
:=
args
[
5
]
if
err
:=
broker
.
checkIndex
(
stub
,
sourceChainID
,
sequenceNum
,
innerMeta
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
if
err
:=
broker
.
markInCounter
(
stub
,
sourceChainID
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
splitedCID
:=
strings
.
Split
(
targetCID
,
delimiter
)
if
len
(
splitedCID
)
!=
2
{
return
errorResponse
(
fmt
.
Sprintf
(
"Target chaincode id %s is not valid"
,
targetCID
))
}
b
:=
util
.
ToChaincodeArgs
(
"interchainCharge"
,
sender
,
receiver
,
amount
)
response
:=
stub
.
InvokeChaincode
(
splitedCID
[
1
],
b
,
splitedCID
[
0
])
if
response
.
Status
!=
shim
.
OK
{
return
errorResponse
(
fmt
.
Sprintf
(
"invoke chaincode '%s' err: %s"
,
splitedCID
[
1
],
response
.
Message
))
}
// persist execution result
key
:=
broker
.
inMsgKey
(
sourceChainID
,
sequenceNum
)
if
err
:=
stub
.
PutState
(
key
,
response
.
Payload
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
return
successResponse
(
nil
)
}
func
(
broker
*
Broker
)
interchainConfirm
(
stub
shim
.
ChaincodeStubInterface
,
args
[]
string
)
pb
.
Response
{
// check args
if
len
(
args
)
<
6
{
return
errorResponse
(
"incorrect number of arguments, expecting 6"
)
}
sourceChainID
:=
args
[
0
]
sequenceNum
:=
args
[
1
]
targetCID
:=
args
[
2
]
status
:=
args
[
3
]
receiver
:=
args
[
4
]
amount
:=
args
[
5
]
if
err
:=
broker
.
checkIndex
(
stub
,
sourceChainID
,
sequenceNum
,
callbackMeta
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
idx
,
err
:=
strconv
.
ParseUint
(
sequenceNum
,
10
,
64
)
if
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
if
err
:=
broker
.
markCallbackCounter
(
stub
,
sourceChainID
,
idx
);
err
!=
nil
{
return
errorResponse
(
err
.
Error
())
}
// confirm interchain tx execution
if
status
==
"true"
{
return
successResponse
(
nil
)
}
splitedCID
:=
strings
.
Split
(
targetCID
,
delimiter
)
if
len
(
splitedCID
)
!=
2
{
return
errorResponse
(
fmt
.
Sprintf
(
"Target chaincode id %s is not valid"
,
targetCID
))
}
b
:=
util
.
ToChaincodeArgs
(
"interchainRollback"
,
receiver
,
amount
)
response
:=
stub
.
InvokeChaincode
(
splitedCID
[
1
],
b
,
splitedCID
[
0
])
if
response
.
Status
!=
shim
.
OK
{
return
errorResponse
(
fmt
.
Sprintf
(
"invoke chaincode '%s' err: %s"
,
splitedCID
[
1
],
response
.
Message
))
}
return
successResponse
(
nil
)
}
example/contracts/src/data_swapper/data_swapper.go
View file @
84277dbf
...
...
@@ -10,9 +10,9 @@ import (
)
const
(
channelID
=
"mychannel"
brokerContractName
=
"broker"
interchainInvokeFunc
=
"InterchainDataSwapInvoke
"
channelID
=
"mychannel"
brokerContractName
=
"broker"
emitInterchainEventFunc
=
"EmitInterchainEvent
"
)
type
DataSwapper
struct
{}
...
...
@@ -65,9 +65,8 @@ func (s *DataSwapper) get(stub shim.ChaincodeStubInterface, args []string) pb.Re
// args[0]: destination appchain id
// args[1]: destination contract address
// args[2]: key
b
:=
util
.
ToChaincodeArgs
(
interchainInvokeFunc
,
args
[
0
],
args
[
1
],
args
[
2
]
)
b
:=
util
.
ToChaincodeArgs
(
emitInterchainEventFunc
,
args
[
0
],
args
[
1
],
"interchainGet"
,
args
[
2
],
"interchainSet"
,
args
[
2
],
""
,
""
)
response
:=
stub
.
InvokeChaincode
(
brokerContractName
,
b
,
channelID
)
if
response
.
Status
!=
shim
.
OK
{
return
shim
.
Error
(
fmt
.
Errorf
(
"invoke broker chaincode %s error: %s"
,
brokerContractName
,
response
.
Message
)
.
Error
())
}
...
...
example/contracts/src/transfer/transfer.go
View file @
84277dbf
...
...
@@ -11,9 +11,9 @@ import (
)
const
(
channelID
=
"mychannel"
brokerContractName
=
"broker"
interchainInvokeFunc
=
"InterchainTransferInvoke
"
channelID
=
"mychannel"
brokerContractName
=
"broker"
emitInterchainEventFunc
=
"EmitInterchainEvent
"
)
type
Transfer
struct
{}
...
...
@@ -121,9 +121,10 @@ func (t *Transfer) transfer(stub shim.ChaincodeStubInterface, args []string) pb.
return
shim
.
Error
(
err
.
Error
())
}
b
:=
util
.
ToChaincodeArgs
(
interchainInvokeFunc
,
dest
,
address
,
sender
,
receiver
,
amountArg
)
args
:=
strings
.
Join
([]
string
{
sender
,
receiver
,
amountArg
},
","
)
argsRb
:=
strings
.
Join
([]
string
{
sender
,
amountArg
},
","
)
b
:=
util
.
ToChaincodeArgs
(
emitInterchainEventFunc
,
dest
,
address
,
"interchainCharge"
,
args
,
""
,
""
,
"interchainRollback"
,
argsRb
)
response
:=
stub
.
InvokeChaincode
(
brokerContractName
,
b
,
channelID
)
if
response
.
Status
!=
shim
.
OK
{
return
shim
.
Error
(
fmt
.
Errorf
(
"invoke broker chaincode %s"
,
response
.
Message
)
.
Error
())
}
...
...
go.mod
View file @
84277dbf
...
...
@@ -3,9 +3,12 @@ module github.com/meshplus/pier-client-fabric
go 1.13
require (
github.com/OneOfOne/xxhash v1.2.5 // indirect
github.com/Rican7/retry v0.1.0
github.com/VividCortex/gohistogram v1.0.0 // indirect
github.com/cloudflare/cfssl v0.0.0-20190409034051-768cd563887f
github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa // indirect
github.com/ethereum/go-ethereum v1.9.18 // indirect
github.com/go-logfmt/logfmt v0.4.0 // indirect
github.com/golang/protobuf v1.4.0
github.com/google/certificate-transparency-go v1.1.0 // indirect
...
...
@@ -15,10 +18,11 @@ require (
github.com/hyperledger/fabric-lib-go v1.0.0 // indirect
github.com/hyperledger/fabric-protos-go v0.0.0-20200330074707-cfe579e86986
github.com/hyperledger/fabric-sdk-go v1.0.0-alpha5
github.com/meshplus/bitxhub-kit v1.0.1-0.20200813124031-6f6bdc99564f
github.com/meshplus/bitxhub-model v1.0.0-rc4.0.20200731025300-2bb1717059e0
github.com/meshplus/pier v1.1.0-rc1.0.20200824115625-bb57600455be
github.com/sirupsen/logrus v1.5.0
github.com/meshplus/bitxhub v1.0.0-rc2 // indirect
github.com/meshplus/bitxhub-kit v1.1.2-0.20201203072410-8a0383a6870d
github.com/meshplus/bitxhub-model v1.1.2-0.20210312014622-c3ad532b64ad
github.com/meshplus/pier v1.5.1-0.20210312103925-148435c71325
github.com/sirupsen/logrus v1.6.0
github.com/spf13/viper v1.6.1
sigs.k8s.io/yaml v1.2.0 // indirect
)
...
...
go.sum
View file @
84277dbf
This diff is collapsed.
Click to expand it.
receipt.go
View file @
84277dbf
...
...
@@ -20,13 +20,19 @@ func (c *Client) generateCallback(original *pb.IBTP, args [][]byte, proof []byte
if
err
:=
originalContent
.
Unmarshal
(
pd
.
Content
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"ibtp payload unmarshal: %w"
,
err
)
}
content
:=
&
pb
.
Content
{
SrcContractId
:
originalContent
.
DstContractId
,
DstContractId
:
originalContent
.
SrcContractId
,
Func
:
originalContent
.
Callback
,
Args
:
args
,
}
if
status
{
content
.
Func
=
originalContent
.
Callback
content
.
Args
=
append
(
originalContent
.
ArgsCb
,
args
...
)
}
else
{
content
.
Func
=
originalContent
.
Rollback
content
.
Args
=
originalContent
.
ArgsRb
}
b
,
err
:=
content
.
Marshal
()
if
err
!=
nil
{
return
nil
,
err
...
...
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