diff --git a/core/blockchain_test.go b/core/blockchain_test.go index d43212247..af8b1c7cc 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -1058,97 +1058,3 @@ func TestCanonicalBlockRetrieval(t *testing.T) { blockchain.InsertChain(types.Blocks{chain[i]}) } } - -type privateTestTx struct { - *types.Transaction - private bool -} - -func (ptx *privateTestTx) IsPrivate() bool { return ptx.private } - -// Tests if the canonical block can be fetched from the database during chain insertion. -func TestPrivateTransactions(t *testing.T) { - var ( - db, _ = ethdb.NewMemDatabase() - key, _ = crypto.GenerateKey() - evmux = &event.TypeMux{} - blockchain, _ = NewBlockChain(db, testChainConfig(), FakePow{}, evmux, false) - header = &types.Header{} - gp = new(GasPool).AddGas(big.NewInt(2000000)) - err error - ) - publicState, err := state.New(common.Hash{}, db) - if err != nil { - t.Fatal(err) - } - privateState, err := state.New(common.Hash{}, db) - if err != nil { - t.Fatal(err) - } - prvContractAddr := common.Address{1} - pubContractAddr := common.Address{2} - - /* gllc - asm { - PUSH1 10 - PUSH1 0 - SSTORE - } - */ - privateState.SetCode(prvContractAddr, common.Hex2Bytes("600a60005500")) - privateState.SetState(prvContractAddr, common.Hash{}, common.Hash{9}) - publicState.SetCode(pubContractAddr, common.Hex2Bytes("601460005500")) - publicState.SetState(pubContractAddr, common.Hash{}, common.Hash{19}) - - // Private transaction 1 - ptx := privateTestTx{private: true} - ptx.Transaction, err = types.NewTransaction(0, prvContractAddr, new(big.Int), big.NewInt(1000000), new(big.Int), nil).SignECDSA(key) - if err != nil { - t.Fatal(err) - } - _, _, err = ApplyMessage(NewEnv(publicState, privateState, &ChainConfig{}, blockchain, ptx.Transaction, header, vm.Config{}), ptx, gp) - 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) - } - - // Public transaction 1 - ptx = privateTestTx{private: false} - ptx.Transaction, err = types.NewTransaction(1, pubContractAddr, new(big.Int), big.NewInt(1000000), new(big.Int), nil).SignECDSA(key) - if err != nil { - t.Fatal(err) - } - _, _, err = ApplyMessage(NewEnv(publicState, publicState, &ChainConfig{}, blockchain, ptx.Transaction, header, vm.Config{}), ptx, gp) - 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 - ptx = privateTestTx{private: true} - ptx.Transaction, err = types.NewTransaction(2, prvContractAddr, new(big.Int), big.NewInt(1000000), new(big.Int), nil).SignECDSA(key) - if err != nil { - t.Fatal(err) - } - _, _, err = ApplyMessage(NewEnv(publicState, privateState, &ChainConfig{}, blockchain, ptx.Transaction, header, vm.Config{}), ptx, gp) - 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) - } - - 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") - } -} diff --git a/core/call_helper.go b/core/call_helper.go new file mode 100644 index 000000000..107c7a112 --- /dev/null +++ b/core/call_helper.go @@ -0,0 +1,86 @@ +package core + +import ( + "crypto/ecdsa" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" +) + +// privateTestTx stubs transaction so that it can be flagged as private or not +type privateTestTx struct { + *types.Transaction + private bool +} + +// IsPrivate returns whether the transaction should be considered privat. +func (ptx *privateTestTx) IsPrivate() bool { return ptx.private } + +// callHelper makes it easier to do proper calls and use the state transition object. +// It also manages the nonces of the caller and keeps private and public state, which +// can be freely modified outside of the helper. +type callHelper struct { + db ethdb.Database + + nonces map[common.Address]uint64 + header types.Header + gp *GasPool + + PrivateState, PublicState *state.StateDB +} + +// TxNonce returns the pending nonce +func (cg *callHelper) TxNonce(addr common.Address) uint64 { + return cg.nonces[addr] +} + +// MakeCall makes does a call to the recipient using the given input. It can switch between private and public +// by setting the private boolean flag. It returns an error if the call failed. +func (cg *callHelper) MakeCall(private bool, key *ecdsa.PrivateKey, to common.Address, input []byte) error { + var ( + from = crypto.PubkeyToAddress(key.PublicKey) + ptx = privateTestTx{private: private} + err error + ) + ptx.Transaction, err = types.NewTransaction(cg.TxNonce(from), to, new(big.Int), big.NewInt(1000000), new(big.Int), input).SignECDSA(key) + if err != nil { + return err + } + defer func() { cg.nonces[from]++ }() + + publicState, privateState := cg.PublicState, cg.PrivateState + if !private { + privateState = publicState + } + _, _, err = ApplyMessage(NewEnv(publicState, privateState, &ChainConfig{}, nil, ptx.Transaction, &cg.header, vm.Config{}), ptx, cg.gp) + if err != nil { + return err + } + return nil +} + +// MakeCallHelper returns a new callHelper +func MakeCallHelper() *callHelper { + db, _ := ethdb.NewMemDatabase() + + publicState, err := state.New(common.Hash{}, db) + if err != nil { + panic(err) + } + privateState, err := state.New(common.Hash{}, db) + if err != nil { + panic(err) + } + cg := &callHelper{ + nonces: make(map[common.Address]uint64), + gp: new(GasPool).AddGas(big.NewInt(5000000)), + PublicState: publicState, + PrivateState: privateState, + } + return cg +} diff --git a/core/private_state_test.go b/core/private_state_test.go new file mode 100644 index 000000000..27678b6e9 --- /dev/null +++ b/core/private_state_test.go @@ -0,0 +1,101 @@ +package core + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +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()) +} + +func TestPrivateTransaction(t *testing.T) { + var ( + key, _ = crypto.GenerateKey() + helper = MakeCallHelper() + privateState = helper.PrivateState + publicState = helper.PublicState + ) + + prvContractAddr := common.Address{1} + pubContractAddr := common.Address{2} + /* gllc + asm { + PUSH1 10 + PUSH1 0 + SSTORE + } + */ + privateState.SetCode(prvContractAddr, common.Hex2Bytes("600a60005500")) + privateState.SetState(prvContractAddr, common.Hash{}, common.Hash{9}) + publicState.SetCode(pubContractAddr, common.Hex2Bytes("601460005500")) + publicState.SetState(pubContractAddr, common.Hash{}, common.Hash{19}) + + // Private transaction 1 + err := helper.MakeCall(true, key, prvContractAddr, nil) + 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) + } + + // 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") + } +}