Squashed commits.

* Remove old note about testing with constellation.
* Review: forceParseRfc3339 -> mustParseRfc3339
* Add isQuorum parameter to SignTx.
* Re-enable TestLogReorgs.
* Fix `SignTx` calls in executables other than geth.
* Bring vm closer to public ethereum implementation.
* Restore all original Ethereum tests.
* Update for new event interface.
* Tweak core / EVM.
* Improve "TX failed" error message.
* Fix "err creating contract" bug.
* Don't log "invalid mix digest" in Quorum.
* Fix public / private bug.
  A private contract calling a method on a public contract would create a
  ghost of the private contract on public state due to a value transfer of
  0.
* Clearer naming in minter / work.commitTransactions.
* Remove unused log import.
* Remove unused `enableQuorumChecks` flag.
This commit is contained in:
Joel Burget 2017-08-16 11:53:24 -04:00
parent 3f18431248
commit 875c10b2d3
27 changed files with 235 additions and 300 deletions

View File

@ -1,32 +1,5 @@
# Hacking on Quorum / various notes
## Testing with Constellation
### `tm.conf`
Replace with appropriate absolute paths:
TODO(joel): figure out how to use relative paths
```
url = "http://127.0.0.1:9000/"
port = 9000
socket = "/Users/joel/go/src/github.com/ethereum/go-ethereum/qdata/tm.ipc"
othernodes = []
storage = "/Users/joel/go/src/github.com/ethereum/go-ethereum/qdata/constellation"
publickeys = ["/Users/joel/go/src/github.com/ethereum/go-ethereum/qdata/test.pub"]
privatekeys = ["/Users/joel/go/src/github.com/ethereum/go-ethereum/qdata/test.key"]
```
Run constellation:
```
> mkdir qdata
> constellation-node tm.conf
```
Now you should be able to run the private state tests as well: `env PRIVATE_CONFIG=(pwd)/tm.conf go test ./...`.
## How does private state work?
Let's look at the EVM structure:

View File

@ -111,7 +111,7 @@ type Wallet interface {
// about which fields or actions are needed. The user may retry by providing
// the needed details via SignTxWithPassphrase, or by other means (e.g. unlock
// the account in a keystore).
SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
SignTx(account Account, tx *types.Transaction, chainID *big.Int, isQuorum bool) (*types.Transaction, error)
// SignHashWithPassphrase requests the wallet to sign the given hash with the
// given passphrase as extra authentication information.

View File

@ -268,7 +268,7 @@ func (ks *KeyStore) SignHash(a accounts.Account, hash []byte) ([]byte, error) {
}
// SignTx signs the given transaction with the requested account.
func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int, isQuorum bool) (*types.Transaction, error) {
// Look up the key to sign with and abort if it cannot be found
ks.mu.RLock()
defer ks.mu.RUnlock()
@ -278,7 +278,7 @@ func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *b
return nil, ErrLocked
}
// Depending on the presence of the chain ID, sign with EIP155 or homestead
if chainID != nil { // && !params.IsQuorum {
if chainID != nil && !isQuorum {
return types.SignTx(tx, types.NewEIP155Signer(chainID), unlockedKey.PrivateKey)
}
return types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey)

View File

@ -98,7 +98,7 @@ func (w *keystoreWallet) SignHash(account accounts.Account, hash []byte) ([]byte
// with the given account. If the wallet does not wrap this particular account,
// an error is returned to avoid account leakage (even though in theory we may
// be able to sign via our shared keystore backend).
func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int, isQuorum bool) (*types.Transaction, error) {
// Make sure the requested account is contained within
if account.Address != w.account.Address {
return nil, accounts.ErrUnknownAccount
@ -107,7 +107,7 @@ func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction,
return nil, accounts.ErrUnknownAccount
}
// Account seems valid, request the keystore to sign
return w.keystore.SignTx(account, tx, chainID)
return w.keystore.SignTx(account, tx, chainID, isQuorum)
}
// SignHashWithPassphrase implements accounts.Wallet, attempting to sign the

View File

@ -25,6 +25,7 @@ import (
"sync"
"time"
"errors"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
@ -508,10 +509,14 @@ func (w *wallet) SignHash(account accounts.Account, hash []byte) ([]byte, error)
// Note, if the version of the Ethereum application running on the Ledger wallet is
// too old to sign EIP-155 transactions, but such is requested nonetheless, an error
// will be returned opposed to silently signing in Homestead mode.
func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int, isQuorum bool) (*types.Transaction, error) {
w.stateLock.RLock() // Comms have own mutex, this is for the state fields
defer w.stateLock.RUnlock()
if isQuorum {
return nil, errors.New("Signing Quorum transactions with a USB wallet not yet supported")
}
// If the wallet is closed, abort
if w.device == nil {
return nil, accounts.ErrWalletClosed
@ -558,5 +563,5 @@ func (w *wallet) SignHashWithPassphrase(account accounts.Account, passphrase str
// transaction with the given account using passphrase as extra authentication.
// Since USB wallets don't rely on passphrases, these are silently ignored.
func (w *wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
return w.SignTx(account, tx, chainID)
return w.SignTx(account, tx, chainID, false)
}

View File

@ -446,7 +446,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
amount = new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(msg.Tier)), nil))
tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, big.NewInt(21000), f.price, nil)
signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainId)
signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainId, false)
if err != nil {
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
f.lock.Unlock()

View File

@ -30,7 +30,6 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
set "gopkg.in/fatih/set.v0"
)
@ -40,7 +39,7 @@ var (
blockReward *big.Int = big.NewInt(5e+18) // Block reward in wei for successfully mining a block
maxUncles = 2 // Maximum number of uncles allowed in a single block
nanosecond2017Timestamp = forceParseRfc3339("2017-01-01T00:00:00+00:00").UnixNano()
nanosecond2017Timestamp = mustParseRfc3339("2017-01-01T00:00:00+00:00").UnixNano()
)
// Various error messages to mark blocks invalid. These should be private to
@ -60,7 +59,7 @@ var (
errInvalidPoW = errors.New("invalid proof-of-work")
)
func forceParseRfc3339(str string) time.Time {
func mustParseRfc3339(str string) time.Time {
time, err := time.Parse(time.RFC3339, str)
if err != nil {
panic("unexpected failure to parse rfc3339 timestamp: " + str)
@ -505,13 +504,8 @@ func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Head
size = 32 * 1024
}
digest, result := hashimotoLight(size, cache, header.HashNoNonce().Bytes(), header.Nonce.Uint64())
if !bytes.Equal(header.MixDigest[:], digest) {
if isQuorum {
log.Info("invalid mix digest", "calculated", fmt.Sprintf("%x", digest), "in header", fmt.Sprintf("%x", header.MixDigest[:]))
} else {
return errInvalidMixDigest
}
if !isQuorum && !bytes.Equal(header.MixDigest[:], digest) {
return errInvalidMixDigest
}
target := new(big.Int).Div(maxUint256, header.Difficulty)
if new(big.Int).SetBytes(result).Cmp(target) > 0 {

View File

@ -35,8 +35,6 @@ type BlockValidator struct {
config *params.ChainConfig // Chain configuration options
bc *BlockChain // Canonical block chain
engine consensus.Engine // Consensus engine used for validating
enableQuorumChecks bool // indication if the signature and vote count is checked (disabled for testing purposes)
}
// NewBlockValidator returns a new block validator which is safe for re-use
@ -102,43 +100,6 @@ func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *stat
return fmt.Errorf("invalid merkle root (remote: %x local: %x)", header.Root, root)
}
// TODO(joel)
/*
if v.enableQuorumChecks {
// Ensure that the parent block was indeed the one that was voted for in the state of this block.
// The contract enforces that there are enough votes and only votes from parties that are allowed to vote.
var (
gp = new(GasPool).AddGas(common.MaxBig)
to = common.HexToAddress("0x0000000000000000000000000000000000000020")
stateCopy = statedb.Copy()
msg = callmsg{
from: stateCopy.GetOrNewStateObject(common.HexToAddress("0x0000000000000000000000000000000000000000")),
to: &to,
gas: big.NewInt(500000),
gasPrice: common.Big0,
value: common.Big0,
data: common.Hex2Bytes(fmt.Sprintf("559c390c%064x", block.Number())), // call getCanonHash(uint256)
}
vmenv = NewEnv(stateCopy, stateCopy, v.config, v.bc, msg, block.Header(), v.config.VmConfig)
)
result, _, _, err := NewStateTransition(vmenv, msg, gp).TransitionDb()
if err != nil {
return err
}
// result holds the hash that was the winning hash according the voting contract
parentHash := common.BytesToHash(result)
if parentHash == (common.Hash{}) {
// too little votes
return fmt.Errorf("block parent could not be verified, ignore block (%d)", block.Number())
}
if block.ParentHash() != parentHash {
return fmt.Errorf("build on top of unexpected parent, expected %s, got %s", parentHash.Hex(), block.ParentHash().Hex())
}
}
*/
return nil
}

View File

@ -118,8 +118,7 @@ type BlockChain struct {
badBlocks *lru.Cache // Bad block cache
privateStateCache state.Database // Private state database to reuse between imports (contains state cache)
chainEvents chan interface{} // Serialized chain insertion events
privateStateCache state.Database // Private state database to reuse between imports (contains state cache)
}
// NewBlockChain returns a fully initialised block chain using information
@ -146,7 +145,6 @@ func NewBlockChain(chainDb ethdb.Database, config *params.ChainConfig, engine co
badBlocks: badBlocks,
privateStateCache: state.NewDatabase(chainDb),
chainEvents: make(chan interface{}, 20), // Buffered for async publishing
}
bc.SetValidator(NewBlockValidator(config, bc, engine))
bc.SetProcessor(NewStateProcessor(config, bc, engine))
@ -1094,12 +1092,14 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
stats.report(chain, i)
}
// TODO(joel/bryan/quorum):
//
// This should remain *synchronous* so that we can control ordering of
// ChainHeadEvents. This is important for supporting low latency
// (non-Proof-of-Work) consensus mechanisms.
//
bc.PostChainEvents(events, coalescedLogs)
// We currently deadlock when running this synchronously. Fix.
go bc.PostChainEvents(events, coalescedLogs)
return 0, nil
}

View File

@ -22,6 +22,7 @@ import (
"math/rand"
"sync"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
@ -856,7 +857,6 @@ func TestChainTxReorgs(t *testing.T) {
}
}
/*
func TestLogReorgs(t *testing.T) {
var (
@ -986,7 +986,6 @@ done:
}
}
*/
// Tests if the canonical block can be fetched from the database during chain insertion.
func TestCanonicalBlockRetrieval(t *testing.T) {

View File

@ -77,7 +77,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb, privateState *stat
receipt, privateReceipt, _, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, privateState, header, tx, totalUsedGas, cfg)
if err != nil {
return nil, nil, nil, totalUsedGas, err // TODO(joel) s/totalUsedGas/nil ?
return nil, nil, nil, nil, err
}
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
@ -100,7 +100,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb, privateState *stat
// for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid.
func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common.Address, gp *GasPool, statedb, privateState *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, cfg vm.Config) (*types.Receipt, *types.Receipt, *big.Int, error) {
if !tx.IsPrivate() {
if !config.IsQuorum || !tx.IsPrivate() {
privateState = statedb
}

View File

@ -519,13 +519,11 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
if !isQuorum && !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
return ErrUnderpriced
}
// Ensure the transaction adheres to nonce ordering
currentState, _, err := pool.blockChain.State()
if err != nil {
return err
}
if currentState.GetNonce(from) > tx.Nonce() {
return ErrNonceTooLow
}

View File

@ -29,6 +29,48 @@ import (
// from bcValidBlockTest.json, "SimpleTx"
func TestBlockEncoding(t *testing.T) {
blockEnc := common.FromHex("f90260f901f9a083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f861f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1c0")
var block Block
if err := rlp.DecodeBytes(blockEnc, &block); err != nil {
t.Fatal("decode error: ", err)
}
check := func(f string, got, want interface{}) {
if !reflect.DeepEqual(got, want) {
t.Errorf("%s mismatch: got %v, want %v", f, got, want)
}
}
check("Difficulty", block.Difficulty(), big.NewInt(131072))
check("GasLimit", block.GasLimit(), big.NewInt(3141592))
check("GasUsed", block.GasUsed(), big.NewInt(21000))
check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1"))
check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498"))
check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017"))
check("Hash", block.Hash(), common.HexToHash("0a5843ac1cb04865017cb35a57b50b07084e5fcee39b5acadade33149f4fff9e"))
check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4))
check("Time", block.Time(), big.NewInt(1426516743))
check("Size", block.Size(), common.StorageSize(len(blockEnc)))
tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), big.NewInt(50000), big.NewInt(10), nil)
tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100"))
fmt.Println(block.Transactions()[0].Hash())
fmt.Println(tx1.data)
fmt.Println(tx1.Hash())
check("len(Transactions)", len(block.Transactions()), 1)
check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash())
ourBlockEnc, err := rlp.EncodeToBytes(&block)
if err != nil {
t.Fatal("encode error: ", err)
}
if !bytes.Equal(ourBlockEnc, blockEnc) {
t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc)
}
}
// TestBlockEncoding from the original Quorum implementation
func TestBlockEncoding2(t *testing.T) {
blockEnc := common.FromHex("f90260f901f9a083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a08bba9707f73a6c9801825706d0814d4ae310e4ff0a16fcd94e72174a176930e4a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f861f85f808082c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1c0")
var block Block
if err := rlp.DecodeBytes(blockEnc, &block); err != nil {

View File

@ -39,6 +39,18 @@ var (
)
rightvrsTx, _ = NewTransaction(
3,
common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b"),
big.NewInt(10),
big.NewInt(2000),
big.NewInt(1),
common.FromHex("5544"),
).WithSignature(
HomesteadSigner{},
common.Hex2Bytes("98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a301"),
)
rightvrsTx2, _ = NewTransaction(
3,
common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b"),
big.NewInt(10),
@ -55,7 +67,7 @@ func TestTransactionSigHash(t *testing.T) {
if emptyTx.SigHash(HomesteadSigner{}) != common.HexToHash("c775b99e7ad12f50d819fcd602390467e28141316969f4b57f0626f74fe3b386") {
t.Errorf("empty transaction hash mismatch, got %x", emptyTx.Hash())
}
if rightvrsTx.SigHash(HomesteadSigner{}) != common.HexToHash("c75e06c2a1b4e254e869653871436fdfa752fd613152b474e6dd36b73a13dae2") {
if rightvrsTx.SigHash(HomesteadSigner{}) != common.HexToHash("fe7a79529ed5f7c3375d06b26b186a8644e0e16c373d7a12be41c62d6042b77a") {
t.Errorf("RightVRS transaction hash mismatch, got %x", rightvrsTx.Hash())
}
}
@ -65,6 +77,28 @@ func TestTransactionEncode(t *testing.T) {
if err != nil {
t.Fatalf("encode error: %v", err)
}
should := common.FromHex("f86103018207d094b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a8255441ca098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa08887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3")
if !bytes.Equal(txb, should) {
t.Errorf("encoded RLP mismatch, got %x", txb)
}
}
// Test from the original quorum implementation
func TestTransactionSigHash2(t *testing.T) {
if emptyTx.SigHash(HomesteadSigner{}) != common.HexToHash("c775b99e7ad12f50d819fcd602390467e28141316969f4b57f0626f74fe3b386") {
t.Errorf("empty transaction hash mismatch, got %x", emptyTx.Hash())
}
if rightvrsTx2.SigHash(HomesteadSigner{}) != common.HexToHash("c75e06c2a1b4e254e869653871436fdfa752fd613152b474e6dd36b73a13dae2") {
t.Errorf("RightVRS transaction hash mismatch, got %x", rightvrsTx2.Hash())
}
}
// Test from the original quorum implementation
func TestTransactionEncode2(t *testing.T) {
txb, err := rlp.EncodeToBytes(rightvrsTx2)
if err != nil {
t.Fatalf("encode error: %v", err)
}
should := common.FromHex("f86103808207d094b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a8255441ca098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa08887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3")
if !bytes.Equal(txb, should) {
t.Errorf("encoded RLP mismatch, got %x", txb)

View File

@ -24,4 +24,6 @@ var (
ErrDepth = errors.New("max call depth exceeded")
ErrTraceLimitReached = errors.New("the number of logs reached the specified limit")
ErrInsufficientBalance = errors.New("insufficient balance for transfer")
ErrReadOnlyValueTransfer = errors.New("VM in read-only mode. Value transfer prohibited.")
)

View File

@ -26,6 +26,17 @@ import (
"github.com/ethereum/go-ethereum/params"
)
// note: Quorum, States, and Value Transfer
//
// In Quorum there is a tricky issue in one specific case when there is call from private state to public state:
// * The state db is selected based on the callee (public)
// * With every call there is an associated value transfer -- in our case this is 0
// * Thus, there is an implicit transfer of 0 value from the caller to callee on the public state
// * However in our scenario the caller is private
// * Thus, the transfer creates a ghost of the private account on the public state with no value, code, or storage
//
// The solution is to skip this transfer of 0 value under Quorum
type (
CanTransferFunc func(StateDB, common.Address, *big.Int) bool
TransferFunc func(StateDB, common.Address, common.Address, *big.Int)
@ -110,8 +121,10 @@ type EVM struct {
privateState PrivateState
states [1027]*state.StateDB // TODO(joel) we should be able to get away with 1024 or maybe 1025
currentStateDepth uint
readOnly bool
readOnlyDepth uint
// This flag has different semantics from the `Interpreter:readOnly` flag (though they interact and could maybe
// be simplified). This is set by Quorum when it's inside a Private State -> Public State read.
quorumReadOnly bool
readOnlyDepth uint
}
// NewEVM retutrns a new EVM . The returned EVM is not thread safe and should
@ -161,33 +174,32 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
return nil, gas, ErrInsufficientBalance
}
// TODO(joel) there's still some work to untangle this
var createAccount bool
if addr == (common.Address{}) {
addr = createAddressAndIncrementNonce(evm, caller)
createAccount = true
}
var (
to = AccountRef(addr)
snapshot = evm.StateDB.Snapshot()
)
if createAccount {
evm.StateDB.CreateAccount(addr)
} else {
if !evm.StateDB.Exist(addr) {
precompiles := PrecompiledContractsHomestead
if evm.ChainConfig().IsMetropolis(evm.BlockNumber) {
precompiles = PrecompiledContractsMetropolis
}
if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {
return nil, gas, nil
}
evm.StateDB.CreateAccount(addr)
if !evm.StateDB.Exist(addr) {
precompiles := PrecompiledContractsHomestead
if evm.ChainConfig().IsMetropolis(evm.BlockNumber) {
precompiles = PrecompiledContractsMetropolis
}
if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {
return nil, gas, nil
}
evm.StateDB.CreateAccount(addr)
}
if evm.ChainConfig().IsQuorum {
// skip transfer if value /= 0 (see note: Quorum, States, and Value Transfer)
if value.Sign() != 0 {
if evm.quorumReadOnly {
return nil, gas, ErrReadOnlyValueTransfer
}
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
}
} else {
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
}
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
// initialise a new contract and set the code that is to be used by the
// E The contract is a scoped environment for this execution context
@ -228,11 +240,10 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
return nil, gas, ErrDepth
}
// Fail if we're trying to transfer more than the available balance
if !evm.CanTransfer(caller.Address(), value) {
if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, gas, ErrInsufficientBalance
}
// TODO(joel) the old version did createAccount / createAddressAndIncrementNonce like Call, but I think unnecessary?
var (
snapshot = evm.StateDB.Snapshot()
to = AccountRef(caller.Address())
@ -344,18 +355,46 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
if evm.depth > int(params.CallCreateDepth) {
return nil, common.Address{}, gas, ErrDepth
}
if !evm.CanTransfer(caller.Address(), value) {
if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, common.Address{}, gas, ErrInsufficientBalance
}
contractAddr = createAddressAndIncrementNonce(evm, caller)
// Get the right state in case of a dual state environment. If a sender
// is a transaction (depth == 0) use the public state to derive the address
// and increment the nonce of the public state. If the sender is a contract
// (depth > 0) use the private state to derive the nonce and increment the
// nonce on the private state only.
//
// If the transaction went to a public contract the private and public state
// are the same.
var creatorStateDb StateDB
if evm.Depth() > 0 {
creatorStateDb = evm.privateState
} else {
creatorStateDb = evm.publicState
}
// Create a new account on the state
nonce := creatorStateDb.GetNonce(caller.Address())
creatorStateDb.SetNonce(caller.Address(), nonce+1)
snapshot := evm.StateDB.Snapshot()
contractAddr = crypto.CreateAddress(caller.Address(), nonce)
evm.StateDB.CreateAccount(contractAddr)
if evm.ChainConfig().IsEIP158(evm.BlockNumber) {
evm.StateDB.SetNonce(contractAddr, 1)
}
evm.Transfer(evm.StateDB, caller.Address(), contractAddr, value)
if evm.ChainConfig().IsQuorum {
// skip transfer if value /= 0 (see note: Quorum, States, and Value Transfer)
if value.Sign() != 0 {
if evm.quorumReadOnly {
return nil, common.Address{}, gas, ErrReadOnlyValueTransfer
}
evm.Transfer(evm.StateDB, caller.Address(), contractAddr, value)
}
} else {
evm.Transfer(evm.StateDB, caller.Address(), contractAddr, value)
}
// initialise a new contract and set the code that is to be used by the
// E The contract is a scoped evmironment for this execution context
@ -416,36 +455,11 @@ func getDualState(env *EVM, addr common.Address) StateDB {
return state
}
// createAddressAndIncrementNonce returns an address based on the caller address and nonce.
//
// It also gets the right state in case of a dual state environment. If a sender
// is a transaction (depth == 0) use the public state to derive the address
// and increment the nonce of the public state. If the sender is a contract
// (depth > 0) use the private state to derive the nonce and increment the
// nonce on the private state only.
//
// If the transaction went to a public contract the private and public state
// are the same.
func createAddressAndIncrementNonce(env *EVM, caller ContractRef) common.Address {
var db StateDB
// check for a dual state in case of quorum.
if env.Depth() > 0 {
db = env.privateState
} else {
db = env.publicState
}
// Increment the callers nonce on the state based on the current depth
nonce := db.GetNonce(caller.Address())
db.SetNonce(caller.Address(), nonce+1)
return crypto.CreateAddress(caller.Address(), nonce)
}
func (env *EVM) PublicState() PublicState { return env.publicState }
func (env *EVM) PrivateState() PrivateState { return env.privateState }
func (env *EVM) Push(statedb StateDB) {
if env.privateState != statedb {
env.readOnly = true
env.quorumReadOnly = true
env.readOnlyDepth = env.currentStateDepth
}
@ -458,18 +472,14 @@ func (env *EVM) Push(statedb StateDB) {
}
func (env *EVM) Pop() {
env.currentStateDepth--
if env.readOnly && env.currentStateDepth == env.readOnlyDepth {
env.readOnly = false
if env.quorumReadOnly && env.currentStateDepth == env.readOnlyDepth {
env.quorumReadOnly = false
}
env.StateDB = env.states[env.currentStateDepth-1]
}
func (env *EVM) Depth() int { return env.depth }
func (self *EVM) CanTransfer(from common.Address, balance *big.Int) bool {
return self.StateDB.GetBalance(from).Cmp(balance) >= 0
}
// We only need to revert the current state because when we call from private
// public state it's read only, there wouldn't be anything to reset.
// (A)->(B)->C->(B): A failure in (B) wouldn't need to reset C, as C was flagged

View File

@ -154,7 +154,7 @@ func (in *Interpreter) Run(snapshot int, contract *Contract, input []byte) (ret
// Get the memory location of pc
op = contract.GetOp(pc)
if in.evm.readOnly && op.isMutating() {
if in.evm.quorumReadOnly && op.isMutating() {
return nil, fmt.Errorf("VM in read-only mode. Mutating opcode prohibited")
}

View File

@ -521,6 +521,7 @@ func StringToOp(str string) OpCode {
func (op OpCode) isMutating() bool {
switch op {
// TODO(joel): REVERT?
case SELFDESTRUCT, CREATE, SSTORE, LOG0, LOG1, LOG2, LOG3, LOG4:
return true
default:

View File

@ -223,18 +223,30 @@ type EthApiState struct {
}
func (s EthApiState) GetBalance(addr common.Address) *big.Int {
if s.privateState.Exist(addr) {
return s.privateState.GetBalance(addr)
}
return s.state.GetBalance(addr)
}
func (s EthApiState) GetCode(addr common.Address) []byte {
if s.privateState.Exist(addr) {
return s.privateState.GetCode(addr)
}
return s.state.GetCode(addr)
}
func (s EthApiState) GetState(a common.Address, b common.Hash) common.Hash {
if s.privateState.Exist(a) {
return s.privateState.GetState(a, b)
}
return s.state.GetState(a, b)
}
func (s EthApiState) GetNonce(addr common.Address) uint64 {
if s.privateState.Exist(addr) {
return s.privateState.GetNonce(addr)
}
return s.state.GetNonce(addr)
}

View File

@ -830,7 +830,7 @@ type RPCTransaction struct {
// newRPCTransaction returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available).
func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction {
var signer types.Signer = types.FrontierSigner{}
var signer types.Signer = types.HomesteadSigner{}
if tx.Protected() {
signer = types.NewEIP155Signer(tx.ChainId())
}
@ -1000,7 +1000,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(hash common.Hash) (map[
}
receipt, _, _, _ := core.GetReceipt(s.b.ChainDb(), hash) // Old receipts don't have the lookup data available
var signer types.Signer = types.FrontierSigner{}
var signer types.Signer = types.HomesteadSigner{}
if tx.Protected() {
signer = types.NewEIP155Signer(tx.ChainId())
}
@ -1041,10 +1041,12 @@ func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transacti
}
// Request the wallet to sign the transaction
var chainID *big.Int
isQuorum := false
if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
chainID = config.ChainId
isQuorum = true
}
return wallet.SignTx(account, tx, chainID)
return wallet.SignTx(account, tx, chainID, isQuorum)
}
// SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool.
@ -1155,10 +1157,12 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
tx := args.toTransaction()
var chainID *big.Int
isQuorum := false
if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
chainID = config.ChainId
isQuorum = true
}
signed, err := wallet.SignTx(account, tx, chainID)
signed, err := wallet.SignTx(account, tx, chainID, isQuorum)
if err != nil {
return common.Hash{}, err
}

View File

@ -85,7 +85,7 @@ func TestAccountManagement(t *testing.T) {
if err := ks.Unlock(signer, "Signer password"); err != nil {
t.Fatalf("Failed to unlock account: %v", err)
}
if _, err := ks.SignTx(signer, tx, chain); err != nil {
if _, err := ks.SignTx(signer, tx, chain, false); err != nil {
t.Fatalf("Failed to sign with unlocked account: %v", err)
}
if err := ks.Lock(signer.Address); err != nil {
@ -95,7 +95,7 @@ func TestAccountManagement(t *testing.T) {
if err := ks.TimedUnlock(signer, "Signer password", time.Second); err != nil {
t.Fatalf("Failed to time unlock account: %v", err)
}
if _, err := ks.SignTx(signer, tx, chain); err != nil {
if _, err := ks.SignTx(signer, tx, chain, false); err != nil {
t.Fatalf("Failed to sign with time unlocked account: %v", err)
}
}

View File

@ -120,7 +120,7 @@ func (ks *KeyStore) SignTx(account *Account, tx *Transaction, chainID *BigInt) (
if chainID == nil { // Null passed from mobile app
chainID = new(BigInt)
}
signed, err := ks.keystore.SignTx(account.account, tx.tx, chainID.bigint)
signed, err := ks.keystore.SignTx(account.account, tx.tx, chainID.bigint, false)
if err != nil {
return nil, err
}

View File

@ -659,7 +659,7 @@ func (pm *ProtocolManager) applyNewChainHead(block *types.Block) {
log.Info("Non-extending block", "block", block.Hash(), "parent", block.ParentHash(), "head", headBlock.Hash())
pm.eventMux.Post(InvalidRaftOrdering{headBlock: headBlock, invalidBlock: block})
pm.minter.invalidRaftOrderingChan <- InvalidRaftOrdering{headBlock: headBlock, invalidBlock: block}
} else {
if existingBlock := pm.blockchain.GetBlockByHash(block.Hash()); nil == existingBlock {
if err := pm.blockchain.Validator().ValidateBody(block); err != nil {

View File

@ -58,6 +58,12 @@ type minter struct {
shouldMine *channels.RingChannel
blockTime time.Duration
speculativeChain *speculativeChain
invalidRaftOrderingChan chan InvalidRaftOrdering
chainHeadChan chan core.ChainHeadEvent
chainHeadSub event.Subscription
txPreChan chan core.TxPreEvent
txPreSub event.Subscription
}
func newMinter(config *params.ChainConfig, eth *RaftService, blockTime time.Duration) *minter {
@ -70,16 +76,18 @@ func newMinter(config *params.ChainConfig, eth *RaftService, blockTime time.Dura
shouldMine: channels.NewRingChannel(1),
blockTime: blockTime,
speculativeChain: newSpeculativeChain(),
invalidRaftOrderingChan: make(chan InvalidRaftOrdering, 1),
chainHeadChan: make(chan core.ChainHeadEvent, 1),
txPreChan: make(chan core.TxPreEvent, 4096),
}
events := minter.mux.Subscribe(
core.ChainHeadEvent{},
core.TxPreEvent{},
InvalidRaftOrdering{},
)
minter.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(minter.chainHeadChan)
minter.txPreSub = eth.TxPool().SubscribeTxPreEvent(minter.txPreChan)
minter.speculativeChain.clear(minter.chain.CurrentBlock())
go minter.eventLoop(events.Chan())
go minter.eventLoop()
go minter.mintingLoop()
return minter
@ -132,10 +140,13 @@ func (minter *minter) updateSpeculativeChainPerInvalidOrdering(headBlock *types.
minter.speculativeChain.unwindFrom(invalidHash, headBlock)
}
func (minter *minter) eventLoop(events <-chan *event.TypeMuxEvent) {
for event := range events {
switch ev := event.Data.(type) {
case core.ChainHeadEvent:
func (minter *minter) eventLoop() {
defer minter.chainHeadSub.Unsubscribe()
defer minter.txPreSub.Unsubscribe()
for {
select {
case ev := <-minter.chainHeadChan:
newHeadBlock := ev.Block
if atomic.LoadInt32(&minter.minting) == 1 {
@ -154,16 +165,22 @@ func (minter *minter) eventLoop(events <-chan *event.TypeMuxEvent) {
minter.mu.Unlock()
}
case core.TxPreEvent:
case <-minter.txPreChan:
if atomic.LoadInt32(&minter.minting) == 1 {
minter.requestMinting()
}
case InvalidRaftOrdering:
case ev := <-minter.invalidRaftOrderingChan:
headBlock := ev.headBlock
invalidBlock := ev.invalidBlock
minter.updateSpeculativeChainPerInvalidOrdering(headBlock, invalidBlock)
// system stopped
case <-minter.chainHeadSub.Err():
return
case <-minter.txPreSub.Err():
return
}
}
}
@ -333,7 +350,7 @@ func (minter *minter) mintNewBlock() {
}
func (env *work) commitTransactions(txes *types.TransactionsByPriceAndNonce, bc *core.BlockChain) (types.Transactions, types.Receipts, types.Receipts, []*types.Log) {
var logs []*types.Log
var allLogs []*types.Log
var committedTxes types.Transactions
var publicReceipts types.Receipts
var privateReceipts types.Receipts
@ -352,25 +369,25 @@ func (env *work) commitTransactions(txes *types.TransactionsByPriceAndNonce, bc
publicReceipt, privateReceipt, err := env.commitTransaction(tx, bc, gp)
switch {
case err != nil:
log.Info("TX failed, will be removed", "hash", tx.Hash().Bytes()[:4], "err", err)
log.Info("TX failed, will be removed", "hash", tx.Hash(), "err", err)
txes.Pop() // skip rest of txes from this account
default:
txCount++
committedTxes = append(committedTxes, tx)
logs = append(logs, publicReceipt.Logs...)
publicReceipts = append(publicReceipts, publicReceipt)
allLogs = append(allLogs, publicReceipt.Logs...)
if privateReceipt != nil {
logs = append(logs, privateReceipt.Logs...)
privateReceipts = append(privateReceipts, privateReceipt)
allLogs = append(allLogs, privateReceipt.Logs...)
}
txes.Shift()
}
}
return committedTxes, publicReceipts, privateReceipts, logs
return committedTxes, publicReceipts, privateReceipts, allLogs
}
func (env *work) commitTransaction(tx *types.Transaction, bc *core.BlockChain, gp *core.GasPool) (*types.Receipt, *types.Receipt, error) {

View File

@ -38,8 +38,8 @@ func TestBlockchain(t *testing.T) {
// Still failing tests
bt.skipLoad(`^bcWalletTest.*_Byzantium$`)
// Skip invalid receipt root hash / invalid nonce quorum failures
bt.skipLoad(`(TransactionSendingToZero|wrongParentHash|wrongMixHash|wrongStateRoot|timestampTooHigh|timestampTooLow|nonceWrong|gasLimitTooLowExactBound|gasLimitTooLow|gasLimitTooHighExactBound|gasLimitTooHigh|diffTooLow2|diffTooLow|diffTooHigh|suicideCoinbase|InternlCallStoreClearsSucces|StoreClearsAndInternlCallStoreClearsOOG|failed_tx_xcf416c53|TransactionSendingToZero|SuicidesAndInternlCallSuicidesSuccess|SuicidesAndInternlCallSuicidesOOG|SuicidesAndInternlCallSuicidesBonusGasAtCallFailed|SuicidesAndInternlCallSuicidesBonusGasAtCall|StoreClearsAndInternlCallStoreClearsSuccess|CallContractToCreateContractOOG).json`)
// TODO(joel): fix Byzantium tests for Quorum
bt.skipLoad(`Byzantium`)
bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) {
if err := bt.checkFailure(t, name, test.Run()); err != nil {

View File

@ -44,9 +44,6 @@ func TestState(t *testing.T) {
st.fails(`^stRevertTest/RevertPrecompiledTouch\.json/Byzantium`, "bug in test")
st.fails(`^stRevertTest/RevertPrefoundEmptyOOG\.json/Byzantium`, "bug in test")
// Skip invalid receipt root hash / invalid nonce quorum failures
st.skipLoad(`(StoreClearsAndInternlCallStoreClearsOOG|TransactionSendingToZero|SuicidesAndInternlCallSuicidesOOG|SuicidesAndInternlCallSuicidesSuccess|SuicidesAndInternlCallSuicidesBonusGasAtCallFailed|SuicidesAndInternlCallSuicidesBonusGasAtCall|StoreClearsAndInternlCallStoreClearsSuccess|InternlCallStoreClearsSucces|InternlCallStoreClearsOOG|failed_tx_xcf416c53|CallContractToCreateContractOOG)\.json`) // EIP-86 is not supported yet
st.walk(t, stateTestDir, func(t *testing.T, name string, test *StateTest) {
for _, subtest := range test.Subtests() {
subtest := subtest

View File

@ -17,31 +17,14 @@
package tests
import (
"math/big"
"os"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core"
"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/log"
"github.com/ethereum/go-ethereum/params"
)
var (
ForceJit bool
EnableJit bool
)
func init() {
log.Root().SetHandler(log.LvlFilterHandler(log.LvlCrit, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
if os.Getenv("JITVM") == "true" {
ForceJit = true
EnableJit = true
}
}
type Account struct {
@ -68,100 +51,3 @@ func (self Log) Topics() [][]byte {
}
return t
}
func insertAccount(state *state.StateDB, saddr string, account Account) {
if common.IsHex(account.Code) {
account.Code = account.Code[2:]
}
addr := common.HexToAddress(saddr)
state.SetCode(addr, common.Hex2Bytes(account.Code))
state.SetNonce(addr, math.MustParseUint64(account.Nonce))
state.SetBalance(addr, math.MustParseBig256(account.Balance))
for a, v := range account.Storage {
state.SetState(addr, common.HexToHash(a), common.HexToHash(v))
}
}
type VmEnv struct {
CurrentCoinbase string
CurrentDifficulty string
CurrentGasLimit string
CurrentNumber string
CurrentTimestamp interface{}
PreviousHash string
}
type VmTest struct {
Callcreates interface{}
//Env map[string]string
Env VmEnv
Exec map[string]string
Transaction map[string]string
Logs []Log
Gas string
Out string
Post map[string]Account
Pre map[string]Account
PostStateRoot string
}
func NewEVMEnvironment(vmTest bool, chainConfig *params.ChainConfig, statedb *state.StateDB, envValues map[string]string, tx map[string]string) (*vm.EVM, core.Message) {
var (
data = common.FromHex(tx["data"])
gas = math.MustParseBig256(tx["gasLimit"])
price = math.MustParseBig256(tx["gasPrice"])
value = math.MustParseBig256(tx["value"])
nonce = math.MustParseUint64(tx["nonce"])
)
origin := common.HexToAddress(tx["caller"])
if len(tx["secretKey"]) > 0 {
key, _ := crypto.HexToECDSA(tx["secretKey"])
origin = crypto.PubkeyToAddress(key.PublicKey)
}
var to *common.Address
if len(tx["to"]) > 2 {
t := common.HexToAddress(tx["to"])
to = &t
}
msg := types.NewMessage(origin, to, nonce, value, gas, price, data, true)
initialCall := true
canTransfer := func(db vm.StateDB, address common.Address, amount *big.Int) bool {
if vmTest {
if initialCall {
initialCall = false
return true
}
}
return core.CanTransfer(db, address, amount)
}
transfer := func(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {
if vmTest {
return
}
core.Transfer(db, sender, recipient, amount)
}
context := vm.Context{
CanTransfer: canTransfer,
Transfer: transfer,
GetHash: func(n uint64) common.Hash {
return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String())))
},
Origin: origin,
Coinbase: common.HexToAddress(envValues["currentCoinbase"]),
BlockNumber: math.MustParseBig256(envValues["currentNumber"]),
Time: math.MustParseBig256(envValues["currentTimestamp"]),
GasLimit: math.MustParseBig256(envValues["currentGasLimit"]),
Difficulty: math.MustParseBig256(envValues["currentDifficulty"]),
GasPrice: price,
}
if context.GasPrice == nil {
context.GasPrice = new(big.Int)
}
return vm.NewEVM(context, statedb, statedb, chainConfig, vm.Config{NoRecursion: vmTest}), msg
}