2016-11-17 05:14:05 -08:00
|
|
|
package core
|
|
|
|
|
|
|
|
import (
|
2017-08-01 05:54:29 -07:00
|
|
|
"bytes"
|
2018-09-11 08:25:24 -07:00
|
|
|
"errors"
|
2016-11-17 05:14:05 -08:00
|
|
|
"fmt"
|
2017-08-01 05:54:29 -07:00
|
|
|
"html/template"
|
|
|
|
"io/ioutil"
|
2016-11-17 05:14:05 -08:00
|
|
|
"math/big"
|
2018-09-11 08:25:24 -07:00
|
|
|
"net"
|
|
|
|
"net/http"
|
2017-08-01 05:54:29 -07:00
|
|
|
"os"
|
|
|
|
osExec "os/exec"
|
|
|
|
"path"
|
2018-09-11 08:25:24 -07:00
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2016-11-17 05:14:05 -08:00
|
|
|
"testing"
|
2017-08-01 05:54:29 -07:00
|
|
|
"time"
|
2016-11-17 05:14:05 -08:00
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/ethereum/go-ethereum/crypto"
|
2017-08-01 05:54:29 -07:00
|
|
|
"github.com/ethereum/go-ethereum/private"
|
|
|
|
"github.com/ethereum/go-ethereum/private/constellation"
|
2016-11-17 05:14:05 -08:00
|
|
|
)
|
|
|
|
|
2017-10-31 15:24:11 -07:00
|
|
|
// callmsg is the message type used for call transactions in the private state test
|
|
|
|
type callmsg struct {
|
2018-05-31 21:23:38 -07:00
|
|
|
addr common.Address
|
|
|
|
to *common.Address
|
|
|
|
gas uint64
|
|
|
|
gasPrice *big.Int
|
|
|
|
value *big.Int
|
|
|
|
data []byte
|
2017-10-31 15:24:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// accessor boilerplate to implement core.Message
|
|
|
|
func (m callmsg) From() common.Address { return m.addr }
|
|
|
|
func (m callmsg) FromFrontier() common.Address { return m.addr }
|
|
|
|
func (m callmsg) Nonce() uint64 { return 0 }
|
|
|
|
func (m callmsg) To() *common.Address { return m.to }
|
|
|
|
func (m callmsg) GasPrice() *big.Int { return m.gasPrice }
|
2018-05-31 21:23:38 -07:00
|
|
|
func (m callmsg) Gas() uint64 { return m.gas }
|
2017-10-31 15:24:11 -07:00
|
|
|
func (m callmsg) Value() *big.Int { return m.value }
|
|
|
|
func (m callmsg) Data() []byte { return m.data }
|
|
|
|
func (m callmsg) CheckNonce() bool { return true }
|
|
|
|
|
2016-11-17 05:14:05 -08:00
|
|
|
func ExampleMakeCallHelper() {
|
|
|
|
var (
|
|
|
|
// setup new pair of keys for the calls
|
|
|
|
key, _ = crypto.GenerateKey()
|
|
|
|
// create a new helper
|
|
|
|
helper = MakeCallHelper()
|
|
|
|
)
|
|
|
|
// Private contract address
|
|
|
|
prvContractAddr := common.Address{1}
|
|
|
|
// Initialise custom code for private contract
|
|
|
|
helper.PrivateState.SetCode(prvContractAddr, common.Hex2Bytes("600a60005500"))
|
|
|
|
// Public contract address
|
|
|
|
pubContractAddr := common.Address{2}
|
|
|
|
// Initialise custom code for public contract
|
|
|
|
helper.PublicState.SetCode(pubContractAddr, common.Hex2Bytes("601460005500"))
|
|
|
|
|
|
|
|
// Make a call to the private contract
|
|
|
|
err := helper.MakeCall(true, key, prvContractAddr, nil)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
}
|
|
|
|
// Make a call to the public contract
|
|
|
|
err = helper.MakeCall(false, key, pubContractAddr, nil)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Output:
|
|
|
|
// Private: 10
|
|
|
|
// Public: 20
|
|
|
|
fmt.Println("Private:", helper.PrivateState.GetState(prvContractAddr, common.Hash{}).Big())
|
|
|
|
fmt.Println("Public:", helper.PublicState.GetState(pubContractAddr, common.Hash{}).Big())
|
|
|
|
}
|
|
|
|
|
2017-08-01 05:54:29 -07:00
|
|
|
var constellationCfgTemplate = template.Must(template.New("t").Parse(`
|
|
|
|
url = "http://127.0.0.1:9000/"
|
|
|
|
port = 9000
|
|
|
|
socketPath = "{{.RootDir}}/qdata/tm1.ipc"
|
|
|
|
otherNodeUrls = []
|
|
|
|
publicKeyPath = "{{.RootDir}}/keys/tm1.pub"
|
|
|
|
privateKeyPath = "{{.RootDir}}/keys/tm1.key"
|
|
|
|
archivalPublicKeyPath = "{{.RootDir}}/keys/tm1a.pub"
|
|
|
|
archivalPrivateKeyPath = "{{.RootDir}}/keys/tm1a.key"
|
|
|
|
storagePath = "{{.RootDir}}/qdata/constellation1"
|
|
|
|
`))
|
|
|
|
|
|
|
|
func runConstellation() (*osExec.Cmd, error) {
|
|
|
|
dir, err := ioutil.TempDir("", "TestPrivateTxConstellationData")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
here, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err = os.MkdirAll(path.Join(dir, "qdata"), 0755); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err = os.Symlink(path.Join(here, "constellation-test-keys"), path.Join(dir, "keys")); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cfgFile, err := os.Create(path.Join(dir, "constellation.cfg"))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = constellationCfgTemplate.Execute(cfgFile, map[string]string{"RootDir": dir})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
constellationCmd := osExec.Command("constellation-node", cfgFile.Name())
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
constellationCmd.Stdout = &stdout
|
|
|
|
constellationCmd.Stderr = &stderr
|
|
|
|
var constellationErr error
|
|
|
|
go func() {
|
|
|
|
constellationErr = constellationCmd.Start()
|
|
|
|
}()
|
|
|
|
// Give the constellation subprocess some time to start.
|
2018-07-15 12:51:05 -07:00
|
|
|
time.Sleep(5 * time.Second)
|
|
|
|
fmt.Println(stdout.String() + stderr.String())
|
2017-08-01 05:54:29 -07:00
|
|
|
if constellationErr != nil {
|
|
|
|
return nil, constellationErr
|
|
|
|
}
|
|
|
|
private.P = constellation.MustNew(cfgFile.Name())
|
|
|
|
return constellationCmd, nil
|
|
|
|
}
|
|
|
|
|
2018-09-11 08:25:24 -07:00
|
|
|
func runTessera() (*osExec.Cmd, error) {
|
|
|
|
tesseraVersion := "0.6"
|
|
|
|
// make sure JRE is available
|
|
|
|
if err := osExec.Command("java").Start(); err != nil {
|
|
|
|
return nil, fmt.Errorf("runTessera: java not available - %s", err.Error())
|
|
|
|
}
|
|
|
|
// download binary from github/release
|
|
|
|
dir, err := ioutil.TempDir("", "tessera")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
resp, err := http.Get(fmt.Sprintf("https://github.com/jpmorganchase/tessera/releases/download/tessera-%s/tessera-app-%s-app.jar", tesseraVersion, tesseraVersion))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
data, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
tesseraJar := filepath.Join(dir, "tessera.jar")
|
|
|
|
if err := ioutil.WriteFile(tesseraJar, data, os.FileMode(0644)); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// create config.json file
|
|
|
|
here, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err = os.MkdirAll(path.Join(dir, "qdata"), 0755); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
tmIPCFile := filepath.Join(dir, "qdata", "tm.ipc")
|
|
|
|
keyData, err := ioutil.ReadFile(filepath.Join(here, "constellation-test-keys", "tm1.key"))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
publicKeyData, err := ioutil.ReadFile(filepath.Join(here, "constellation-test-keys", "tm1.pub"))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
tesseraConfigFile := filepath.Join(dir, "config.json")
|
|
|
|
if err := ioutil.WriteFile(tesseraConfigFile, []byte(fmt.Sprintf(`
|
|
|
|
{
|
|
|
|
"useWhiteList": false,
|
|
|
|
"jdbc": {
|
|
|
|
"username": "sa",
|
|
|
|
"password": "",
|
|
|
|
"url": "jdbc:h2:./qdata/c0/db0;MODE=Oracle;TRACE_LEVEL_SYSTEM_OUT=0"
|
|
|
|
},
|
|
|
|
"server": {
|
|
|
|
"port": 9000,
|
|
|
|
"hostName": "http://localhost",
|
|
|
|
"sslConfig": {
|
|
|
|
"tls": "OFF",
|
|
|
|
"generateKeyStoreIfNotExisted": true,
|
|
|
|
"serverKeyStore": "./qdata/c1/server1-keystore",
|
|
|
|
"serverKeyStorePassword": "quorum",
|
|
|
|
"serverTrustStore": "./qdata/c1/server-truststore",
|
|
|
|
"serverTrustStorePassword": "quorum",
|
|
|
|
"serverTrustMode": "TOFU",
|
|
|
|
"knownClientsFile": "./qdata/c1/knownClients",
|
|
|
|
"clientKeyStore": "./c1/client1-keystore",
|
|
|
|
"clientKeyStorePassword": "quorum",
|
|
|
|
"clientTrustStore": "./c1/client-truststore",
|
|
|
|
"clientTrustStorePassword": "quorum",
|
|
|
|
"clientTrustMode": "TOFU",
|
|
|
|
"knownServersFile": "./qdata/c1/knownServers"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"peer": [
|
|
|
|
{
|
|
|
|
"url": "http://localhost:9000"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"keys": {
|
|
|
|
"passwords": [],
|
|
|
|
"keyData": [
|
|
|
|
{
|
|
|
|
"config": %s,
|
|
|
|
"publicKey": "%s"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
"alwaysSendTo": [],
|
|
|
|
"unixSocketFile": "%s"
|
|
|
|
}
|
|
|
|
`, string(keyData), string(publicKeyData), tmIPCFile)), os.FileMode(0644)); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cmdStatusChan := make(chan error)
|
|
|
|
cmd := osExec.Command("java", "-Xms128M", "-Xmx128M", "-jar", tesseraJar, "-configFile", tesseraConfigFile)
|
|
|
|
// run tessera
|
|
|
|
go func() {
|
|
|
|
err := cmd.Start()
|
|
|
|
cmdStatusChan <- err
|
|
|
|
}()
|
|
|
|
// wait for tessera to come up
|
|
|
|
go func() {
|
|
|
|
waitingErr := errors.New("waiting")
|
|
|
|
checkFunc := func() error {
|
|
|
|
conn, err := net.Dial("unix", tmIPCFile)
|
|
|
|
if err != nil {
|
|
|
|
return waitingErr
|
|
|
|
}
|
|
|
|
if _, err := conn.Write([]byte("GET /upcheck HTTP/1.0\r\n\r\n")); err != nil {
|
|
|
|
return waitingErr
|
|
|
|
}
|
|
|
|
result, err := ioutil.ReadAll(conn)
|
|
|
|
if err != nil || string(result) != "I'm up!" {
|
|
|
|
return waitingErr
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
for {
|
|
|
|
time.Sleep(3 * time.Second)
|
|
|
|
if err := checkFunc(); err != nil && err != waitingErr {
|
|
|
|
cmdStatusChan <- err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
if err := <-cmdStatusChan; err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// wait until tessera is up
|
|
|
|
return cmd, nil
|
|
|
|
}
|
|
|
|
|
2017-10-31 15:24:11 -07:00
|
|
|
// 600a600055600060006001a1
|
|
|
|
// [1] PUSH1 0x0a (store value)
|
|
|
|
// [3] PUSH1 0x00 (store addr)
|
|
|
|
// [4] SSTORE
|
|
|
|
// [6] PUSH1 0x00
|
|
|
|
// [8] PUSH1 0x00
|
|
|
|
// [10] PUSH1 0x01
|
|
|
|
// [11] LOG1
|
|
|
|
//
|
|
|
|
// Store then log
|
2016-11-17 05:14:05 -08:00
|
|
|
func TestPrivateTransaction(t *testing.T) {
|
|
|
|
var (
|
|
|
|
key, _ = crypto.GenerateKey()
|
|
|
|
helper = MakeCallHelper()
|
|
|
|
privateState = helper.PrivateState
|
|
|
|
publicState = helper.PublicState
|
|
|
|
)
|
|
|
|
|
2017-08-01 05:54:29 -07:00
|
|
|
constellationCmd, err := runConstellation()
|
|
|
|
if err != nil {
|
2018-09-11 08:25:24 -07:00
|
|
|
if strings.Contains(err.Error(), "executable file not found") {
|
|
|
|
if constellationCmd, err = runTessera(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-08-01 05:54:29 -07:00
|
|
|
}
|
|
|
|
defer constellationCmd.Process.Kill()
|
|
|
|
|
2016-11-17 05:14:05 -08:00
|
|
|
prvContractAddr := common.Address{1}
|
|
|
|
pubContractAddr := common.Address{2}
|
2017-02-15 22:55:30 -08:00
|
|
|
privateState.SetCode(prvContractAddr, common.Hex2Bytes("600a600055600060006001a1"))
|
2016-11-17 05:14:05 -08:00
|
|
|
privateState.SetState(prvContractAddr, common.Hash{}, common.Hash{9})
|
2017-02-15 22:55:30 -08:00
|
|
|
publicState.SetCode(pubContractAddr, common.Hex2Bytes("6014600055"))
|
2016-11-17 05:14:05 -08:00
|
|
|
publicState.SetState(pubContractAddr, common.Hash{}, common.Hash{19})
|
|
|
|
|
2017-10-31 15:24:11 -07:00
|
|
|
if publicState.Exist(prvContractAddr) {
|
|
|
|
t.Error("didn't expect private contract address to exist on public state")
|
|
|
|
}
|
|
|
|
|
2016-11-17 05:14:05 -08:00
|
|
|
// Private transaction 1
|
2017-08-01 05:54:29 -07:00
|
|
|
err = helper.MakeCall(true, key, prvContractAddr, nil)
|
2016-11-17 05:14:05 -08:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
stateEntry := privateState.GetState(prvContractAddr, common.Hash{}).Big()
|
|
|
|
if stateEntry.Cmp(big.NewInt(10)) != 0 {
|
|
|
|
t.Error("expected state to have 10, got", stateEntry)
|
|
|
|
}
|
2017-02-15 22:55:30 -08:00
|
|
|
if len(privateState.Logs()) != 1 {
|
|
|
|
t.Error("expected private state to have 1 log, got", len(privateState.Logs()))
|
|
|
|
}
|
2017-10-31 15:24:11 -07:00
|
|
|
if len(publicState.Logs()) != 0 {
|
|
|
|
t.Error("expected public state to have 0 logs, got", len(publicState.Logs()))
|
|
|
|
}
|
|
|
|
if publicState.Exist(prvContractAddr) {
|
|
|
|
t.Error("didn't expect private contract address to exist on public state")
|
|
|
|
}
|
|
|
|
if !privateState.Exist(prvContractAddr) {
|
|
|
|
t.Error("expected private contract address to exist on private state")
|
|
|
|
}
|
2016-11-17 05:14:05 -08:00
|
|
|
|
|
|
|
// Public transaction 1
|
|
|
|
err = helper.MakeCall(false, key, pubContractAddr, nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
stateEntry = publicState.GetState(pubContractAddr, common.Hash{}).Big()
|
|
|
|
if stateEntry.Cmp(big.NewInt(20)) != 0 {
|
|
|
|
t.Error("expected state to have 20, got", stateEntry)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Private transaction 2
|
|
|
|
err = helper.MakeCall(true, key, prvContractAddr, nil)
|
|
|
|
stateEntry = privateState.GetState(prvContractAddr, common.Hash{}).Big()
|
|
|
|
if stateEntry.Cmp(big.NewInt(10)) != 0 {
|
|
|
|
t.Error("expected state to have 10, got", stateEntry)
|
|
|
|
}
|
|
|
|
|
|
|
|
if publicState.Exist(prvContractAddr) {
|
|
|
|
t.Error("didn't expect private contract address to exist on public state")
|
|
|
|
}
|
|
|
|
if privateState.Exist(pubContractAddr) {
|
|
|
|
t.Error("didn't expect public contract address to exist on private state")
|
|
|
|
}
|
|
|
|
}
|