diff --git a/core/state_transition.go b/core/state_transition.go index 633fbbcb6..3ab528488 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -188,6 +188,12 @@ func (st *StateTransition) preCheck() error { // TransitionDb will transition the state by applying the current message and // returning the result including the used gas. It returns an error if failed. // An error indicates a consensus issue. +// +// Quorum: +// 1. Intrinsic gas is calculated based on the encrypted payload hash +// and NOT the actual private payload +// 2. For private transactions, we only deduct intrinsic gas from the gas pool +// regardless the current node is party to the transaction or not func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) { if err = st.preCheck(); err != nil { return @@ -231,7 +237,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo var ( leftoverGas uint64 - evm = st.evm + evm = st.evm // vm errors do not effect consensus and are therefor // not assigned to err, except for insufficient balance // error. @@ -255,6 +261,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo //if input is empty for the smart contract call, return if len(data) == 0 && isPrivate { st.refundGas() + st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) return nil, 0, false, nil } diff --git a/core/state_transition_test.go b/core/state_transition_test.go new file mode 100644 index 000000000..06d563540 --- /dev/null +++ b/core/state_transition_test.go @@ -0,0 +1,115 @@ +package core + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/private" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/params" + + testifyassert "github.com/stretchr/testify/assert" +) + +func verifyGasPoolCalculation(t *testing.T, pm private.PrivateTransactionManager) { + assert := testifyassert.New(t) + saved := private.P + defer func() { + private.P = saved + }() + private.P = pm + + txGasLimit := uint64(100000) + gasPool := new(GasPool).AddGas(200000) + // this payload would give us 25288 intrinsic gas + arbitraryEncryptedPayload := "4ab80888354582b92ab442a317828386e4bf21ea4a38d1a9183fbb715f199475269d7686939017f4a6b28310d5003ebd8e012eade530b79e157657ce8dd9692a" + expectedGasPool := new(GasPool).AddGas(174712) // only intrinsic gas is deducted + + db := ethdb.NewMemDatabase() + privateState, _ := state.New(common.Hash{}, state.NewDatabase(db)) + publicState, _ := state.New(common.Hash{}, state.NewDatabase(db)) + msg := privateCallMsg{ + callmsg: callmsg{ + addr: common.Address{2}, + to: &common.Address{}, + value: new(big.Int), + gas: txGasLimit, + gasPrice: big.NewInt(0), + data: common.Hex2Bytes(arbitraryEncryptedPayload), + }, + } + ctx := NewEVMContext(msg, &dualStateTestHeader, nil, &common.Address{}) + evm := vm.NewEVM(ctx, publicState, privateState, params.QuorumTestChainConfig, vm.Config{}) + arbitraryBalance := big.NewInt(100000000) + publicState.SetBalance(evm.Coinbase, arbitraryBalance) + publicState.SetBalance(msg.From(), arbitraryBalance) + + testObject := NewStateTransition(evm, msg, gasPool) + + _, _, failed, err := testObject.TransitionDb() + + assert.NoError(err) + assert.False(failed) + + assert.Equal(new(big.Int).SetUint64(expectedGasPool.Gas()), new(big.Int).SetUint64(gasPool.Gas()), "gas pool must be calculated correctly") + assert.Equal(arbitraryBalance, publicState.GetBalance(evm.Coinbase), "balance must not be changed") + assert.Equal(arbitraryBalance, publicState.GetBalance(msg.From()), "balance must not be changed") +} + +func TestStateTransition_TransitionDb_GasPoolCalculation_whenNonPartyNodeProcessingPrivateTransactions(t *testing.T) { + stubPTM := &StubPrivateTransactionManager{ + responses: map[string][]interface{}{ + "Receive": { + []byte{}, + nil, + }, + }, + } + verifyGasPoolCalculation(t, stubPTM) +} + +func TestStateTransition_TransitionDb_GasPoolCalculation_whenPartyNodeProcessingPrivateTransactions(t *testing.T) { + stubPTM := &StubPrivateTransactionManager{ + responses: map[string][]interface{}{ + "Receive": { + common.Hex2Bytes("600a6000526001601ff300"), + nil, + }, + }, + } + verifyGasPoolCalculation(t, stubPTM) +} + +type privateCallMsg struct { + callmsg +} + +func (pm privateCallMsg) IsPrivate() bool { return true } + +type StubPrivateTransactionManager struct { + responses map[string][]interface{} +} + +func (spm *StubPrivateTransactionManager) Send(data []byte, from string, to []string) ([]byte, error) { + return nil, fmt.Errorf("to be implemented") +} + +func (spm *StubPrivateTransactionManager) SendSignedTx(data []byte, to []string) ([]byte, error) { + return nil, fmt.Errorf("to be implemented") +} + +func (spm *StubPrivateTransactionManager) Receive(data []byte) ([]byte, error) { + res := spm.responses["Receive"] + if err, ok := res[1].(error); ok { + return nil, err + } + if ret, ok := res[0].([]byte); ok { + return ret, nil + } + return nil, nil +}