diff --git a/.travis.yml b/.travis.yml index 7fd276b2c..b938386bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,95 +1,108 @@ -# simplifed version of the upstream travis configuration +# simplified version of the upstream travis configuration. +# automated acceptance test is run[in linux box] for raft, istanbul and clique consensus in parallel as part of every build +# unit test is run in linux and macOS boxes as part of every build language: go go_import_path: github.com/ethereum/go-ethereum -go: 1.9.x -sudo: true -branches: - only: - - /.*/ # everything including tags +sudo: false + env: global: - - BINTRAY_ORGANIZATION=quorumengineering - - BINTRAY_USER=quorumbot - # Bintray API Key - - secure: "QHiPcd3zQoJEsT3VSpxoLVTYwbiYzS8H18EpY7Tk0EqCIfswS2AvRlyRXUxNvCf9ktzpaeXV4b5cPYJ67dwdp5V/O/ARaK5AL6ZjjrTPR1avPnmz/X2VeQEP0aWk1UGMs1nBUj5rzMbIIxlVhpbiITTLAI4Ao0+xRcBi215mDbv271Z7mACEZfXxjaoJA0/3IkbKz9pu1nC7bTjaaExCDAeLp2p8fHi2YQPnBll/7dkn/m1rnsIY9M3KWNCx6xBmQOr1hulrrB6tZoHwFBoDsVTFJFLckPfrWUZsYUgtfWJMQWc6ntv1gFl0f9x6s5fYEphCU2m1JYjEczlQ03B5ro9EyPGKjO7vQxAaFd5nVd2Xf34ZbssEIyXxlSnP/4Gv1GXl9L9aU1Hth9ckYvT5gYP5t/Nw3CDbKD0HelPBvkf8jZwfdlotzFPS2bOZNdl/rJLWgQrX18a/mC3BH9l4TSRz13tbRfo6YcC3Y/uOvG1n4GxzcVaWojAxn86SkknOczPTf2pk9F3JOcGVSYA2R4kGQAe+ErJH2X5g2sh1D5cCYDjQyl5rzWg6P3eK//HYW+mg2+TQ8k2iQVVSwFwrR0Yn4P+5cRDCW9mjtktgq1rTtslj41gSH49Avqr9oXGM2rqdcJPdN8dnmLMrAtmeSUNMMoexiRMmlF2OQKLrW3k=" + - TESSERA_JAR="$HOME/tessera.jar" + +addons: + apt: + update: true + sources: + - sourceline: ppa:ethereum/ethereum + - sourceline: ppa:openjdk-r/ppa + packages: + - openjdk-8-jdk + - dpkg # fixes issue with dpkg-deb error due to travis image + - openssh-client + - dnsutils + - maven + - solc + - jq + - curl + +before_install: +- if [ $TRAVIS_OS_NAME = linux ]; then git clone https://github.com/amalrajmani/quorum-acceptance-tests.git $TRAVIS_HOME/quorum-acceptance-tests; fi; +- if [ $TRAVIS_OS_NAME = linux ]; then sudo chmod 755 $TRAVIS_HOME/quorum-acceptance-tests/src/travis/install-linux.sh; fi; +- if [ $TRAVIS_OS_NAME = linux ]; then sudo chmod 755 $TRAVIS_HOME/quorum-acceptance-tests/src/travis/script-linux.sh; fi; + matrix: include: - - if: tag IS blank - os: linux - dist: xenial - script: - - sudo modprobe fuse - - sudo chmod 666 /dev/fuse - - sudo chown root:$USER /etc/fuse.conf - - go run build/ci.go install - - go run build/ci.go test -coverage $TEST_PACKAGES - - if: tag IS blank - os: osx - osx_image: xcode9.2 # so we don't have to deal with Kernel Extension Consent UI which is never possible in CI - script: - - brew update - - brew install caskroom/cask/brew-cask - - brew cask install osxfuse - - go run build/ci.go install - - go run build/ci.go test -coverage $TEST_PACKAGES + - name: linux-raft + os: linux + dist: trusty + sudo: true + go: 1.10.x - - if: tag IS present - os: linux - dist: xenial - env: OUTPUT_FILE=geth_${TRAVIS_TAG}_linux_amd64.tar.gz - script: - - build/env.sh go run build/ci.go install ./cmd/geth - - sudo mkdir -p /dist - - cd build/bin - - sudo tar cfvz /dist/${OUTPUT_FILE} geth - - if: tag IS present - os: osx - osx_image: xcode9.2 - env: OUTPUT_FILE=geth_${TRAVIS_TAG}_darwin_amd64.tar.gz - script: - - build/env.sh go run build/ci.go install ./cmd/geth - - sudo mkdir -p /dist - - cd build/bin - - sudo tar cfvz /dist/${OUTPUT_FILE} geth + cache: + directories: + - $HOME/.m2 + + env: + - TF_VAR_consensus_mechanism=raft + + install: $TRAVIS_HOME/quorum-acceptance-tests/src/travis/install-linux.sh + + script: $TRAVIS_HOME/quorum-acceptance-tests/src/travis/script-linux.sh + + + - name: linux-istanbul + os: linux + dist: trusty + sudo: true + go: 1.10.x + + directories: + - $HOME/.m2 + + env: + - TF_VAR_consensus_mechanism=istanbul + + install: $TRAVIS_HOME/quorum-acceptance-tests/src/travis/install-linux.sh + + script: $TRAVIS_HOME/quorum-acceptance-tests/src/travis/script-linux.sh + + - name: linux-clique + os: linux + dist: trusty + sudo: true + go: 1.10.x + + directories: + - $HOME/.m2 + + env: + - TF_VAR_consensus_mechanism=clique + + install: $TRAVIS_HOME/quorum-acceptance-tests/src/travis/install-linux.sh + + script: $TRAVIS_HOME/quorum-acceptance-tests/src/travis/script-linux.sh + + - os: linux + dist: trusty + sudo: required + go: 1.10.x + script: + - sudo modprobe fuse + - sudo chmod 666 /dev/fuse + - sudo chown root:$USER /etc/fuse.conf + - go run build/ci.go install + - go run build/ci.go test -coverage $TEST_PACKAGES + + + - os: osx + osx_image: xcode9.2 # so we don't have to deal with Kernel Extension Consent UI which is never possible in CI + go: 1.10.x + sudo: required + script: + - brew update + - brew install caskroom/cask/brew-cask + - brew cask install osxfuse + - go run build/ci.go install + - go run build/ci.go test -coverage $TEST_PACKAGES -before_deploy: - - | - echo "Prepare Bintray descriptor" - export GETH_VERSION=$(cat ${TRAVIS_BUILD_DIR}/VERSION) - export RELEASED_DATE=$(date +'%Y-%m-%d') - sed -e "s/_TRAVIS_TAG_/${TRAVIS_TAG}/g" \ - -e "s/_TRAVIS_BUILD_NUMBER_/${TRAVIS_BUILD_NUMBER}/g" \ - -e "s/_GETH_VERSION_/${GETH_VERSION}/g" \ - -e "s/_RELEASED_DATE_/${RELEASED_DATE}/g" \ - -e "s/_TRAVIS_COMMIT_/${TRAVIS_COMMIT}/g" \ - -e "s/_TRAVIS_JOB_WEB_URL_/${TRAVIS_JOB_WEB_URL//\//\\/}/g" \ - -e "s/_ORGANIZATION_/${BINTRAY_ORGANIZATION}/g" \ - ${TRAVIS_BUILD_DIR}/.bintray.json > /tmp/bintray.json -after_deploy: - - | - published="" - while [ "$published" == "" ]; do - echo "Sleep 5s to wait until ${OUTPUT_FILE} is published" - sleep 5 - result=$(curl -u ${BINTRAY_USER}:${BINTRAY_API_KEY} "https://api.bintray.com/packages/${BINTRAY_ORGANIZATION}/quorum/geth/versions/${TRAVIS_TAG}/files") - echo "$result" - if [[ "$result" == *"${OUTPUT_FILE}"* ]]; then - published="done" - fi - done - - | - echo "Add ${OUTPUT_FILE} to Download List" - curl -u ${BINTRAY_USER}:${BINTRAY_API_KEY} \ - -H "Content-type: application/json" \ - -X PUT \ - --data "{\"list_in_downloads\": true}" \ - https://api.bintray.com/file_metadata/${BINTRAY_ORGANIZATION}/quorum/${TRAVIS_TAG}/${OUTPUT_FILE} -deploy: - provider: bintray - file: /tmp/bintray.json - user: ${BINTRAY_USER} - key: ${BINTRAY_API_KEY} - skip_cleanup: true - on: - tags: true \ No newline at end of file diff --git a/common/compiler/solidity_test.go b/common/compiler/solidity_test.go index 0da3bb337..ae2838673 100644 --- a/common/compiler/solidity_test.go +++ b/common/compiler/solidity_test.go @@ -23,9 +23,10 @@ import ( const ( testSource = ` +pragma solidity ^0.5.0; contract test { /// @notice Will multiply ` + "`a`" + ` by 7. - function multiply(uint a) returns(uint d) { + function multiply(uint a) public returns(uint d) { return a * 7; } } diff --git a/consensus/istanbul/backend/engine_test.go b/consensus/istanbul/backend/engine_test.go index c7d0f7f02..d14032afc 100644 --- a/consensus/istanbul/backend/engine_test.go +++ b/consensus/istanbul/backend/engine_test.go @@ -47,7 +47,7 @@ func newBlockChain(n int) (*core.BlockChain, *backend) { // Use the first key as private key b, _ := New(config, nodeKeys[0], memDB).(*backend) genesis.MustCommit(memDB) - blockchain, err := core.NewBlockChain(memDB, nil, genesis.Config, b, vm.Config{}) + blockchain, err := core.NewBlockChain(memDB, nil, genesis.Config, b, vm.Config{}, nil) if err != nil { panic(err) } @@ -120,7 +120,7 @@ func makeHeader(parent *types.Block, config *istanbul.Config) *types.Header { header := &types.Header{ ParentHash: parent.Hash(), Number: parent.Number().Add(parent.Number(), common.Big1), - GasLimit: core.CalcGasLimit(parent), + GasLimit: core.CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()), GasUsed: 0, Extra: parent.Extra(), Time: new(big.Int).Add(parent.Time(), new(big.Int).SetUint64(config.BlockPeriod)), @@ -131,8 +131,11 @@ func makeHeader(parent *types.Block, config *istanbul.Config) *types.Header { func makeBlock(chain *core.BlockChain, engine *backend, parent *types.Block) *types.Block { block := makeBlockWithoutSeal(chain, engine, parent) - block, _ = engine.Seal(chain, block, nil) - return block + stopCh := make(chan struct{}) + resultCh := make(chan *types.Block, 10) + go engine.Seal(chain, block, resultCh, stopCh) + blk := <-resultCh + return blk } func makeBlockWithoutSeal(chain *core.BlockChain, engine *backend, parent *types.Block) *types.Block { @@ -174,10 +177,15 @@ func TestSealStopChannel(t *testing.T) { eventSub.Unsubscribe() } go eventLoop() - finalBlock, err := engine.Seal(chain, block, stop) - if err != nil { - t.Errorf("error mismatch: have %v, want nil", err) - } + resultCh := make(chan *types.Block, 10) + go func() { + err := engine.Seal(chain, block, resultCh, stop) + if err != nil { + t.Errorf("error mismatch: have %v, want nil", err) + } + }() + + finalBlock := <-resultCh if finalBlock != nil { t.Errorf("block mismatch: have %v, want nil", finalBlock) } @@ -201,7 +209,7 @@ func TestSealCommittedOtherHash(t *testing.T) { } go eventLoop() seal := func() { - engine.Seal(chain, block, nil) + engine.Seal(chain, block, nil, make(chan struct{})) t.Error("seal should not be completed") } go seal() @@ -218,11 +226,16 @@ func TestSealCommitted(t *testing.T) { chain, engine := newBlockChain(1) block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) expectedBlock, _ := engine.updateBlock(engine.chain.GetHeader(block.ParentHash(), block.NumberU64()-1), block) + resultCh := make(chan *types.Block, 10) + go func() { + err := engine.Seal(chain, block, resultCh, make(chan struct{})) - finalBlock, err := engine.Seal(chain, block, nil) - if err != nil { - t.Errorf("error mismatch: have %v, want nil", err) - } + if err != nil { + t.Errorf("error mismatch: have %v, want %v", err, expectedBlock) + } + }() + + finalBlock := <-resultCh if finalBlock.Hash() != expectedBlock.Hash() { t.Errorf("hash mismatch: have %v, want %v", finalBlock.Hash(), expectedBlock.Hash()) } diff --git a/consensus/istanbul/backend/snapshot_test.go b/consensus/istanbul/backend/snapshot_test.go index f7a0ea273..92f25a34b 100644 --- a/consensus/istanbul/backend/snapshot_test.go +++ b/consensus/istanbul/backend/snapshot_test.go @@ -347,7 +347,7 @@ func TestVoting(t *testing.T) { config.Epoch = tt.epoch } engine := New(config, accounts.accounts[tt.validators[0]], db).(*backend) - chain, err := core.NewBlockChain(db, nil, genesis.Config, engine, vm.Config{}) + chain, err := core.NewBlockChain(db, nil, genesis.Config, engine, vm.Config{}, nil) // Assemble a chain of headers from the cast votes headers := make([]*types.Header, len(tt.votes)) diff --git a/consensus/istanbul/core/testbackend_test.go b/consensus/istanbul/core/testbackend_test.go index 741eec853..812056f1f 100644 --- a/consensus/istanbul/core/testbackend_test.go +++ b/consensus/istanbul/core/testbackend_test.go @@ -156,6 +156,10 @@ func (self *testSystemBackend) ParentValidators(proposal istanbul.Proposal) ista return self.peers } +func (sb *testSystemBackend) Close() error { + return nil +} + // ============================================== // // define the struct that need to be provided for integration tests. diff --git a/core/block_validator.go b/core/block_validator.go index 5ebc8e99e..b3fb6e4c6 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -103,8 +103,10 @@ func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *stat return nil } -// CalcGasLimit computes the gas limit of the next block after parent. -// This is miner strategy, not consensus protocol. +// CalcGasLimit computes the gas limit of the next block after parent. It aims +// to keep the baseline gas above the provided floor, and increase it towards the +// ceil if the blocks are full. If the ceil is exceeded, it will always decrease +// the gas allowance. func CalcGasLimit(parent *types.Block, gasFloor, gasCeil uint64) uint64 { // contrib = (parentGasUsed * 3 / 2) / 4096 contrib := (parent.GasUsed() + parent.GasUsed()/2) / params.GasLimitBoundDivisor @@ -123,8 +125,7 @@ func CalcGasLimit(parent *types.Block, gasFloor, gasCeil uint64) uint64 { if limit < params.MinGasLimit { limit = params.MinGasLimit } - // however, if we're now below the target (TargetGasLimit) we increase the - // limit as much as we can (parentGasLimit / 4096 -1) + // If we're outside our allowed gas range, we try to hone towards them if limit < gasFloor { limit = parent.GasLimit() + decay if limit > gasFloor { diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index e63740eb1..02ebbc4c5 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -117,25 +117,70 @@ func gasReturnDataCopy(gt params.GasTable, evm *EVM, contract *Contract, stack * func gasSStore(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( - db = getDualState(evm, contract.Address()) - y, x = stack.Back(1), stack.Back(0) - val = db.GetState(contract.Address(), common.BigToHash(x)) + db = getDualState(evm, contract.Address()) + y, x = stack.Back(1), stack.Back(0) + current = db.GetState(contract.Address(), common.BigToHash(x)) ) - // This checks for 3 scenario's and calculates gas accordingly - // 1. From a zero-value address to a non-zero value (NEW VALUE) - // 2. From a non-zero value address to a zero-value address (DELETE) - // 3. From a non-zero to a non-zero (CHANGE) - if val == (common.Hash{}) && y.Sign() != 0 { - // 0 => non 0 - return params.SstoreSetGas, nil - } else if val != (common.Hash{}) && y.Sign() == 0 { - // non 0 => 0 - db.AddRefund(params.SstoreRefundGas) - return params.SstoreClearGas, nil - } else { - // non 0 => non 0 (or 0 => 0) - return params.SstoreResetGas, nil + // The legacy gas metering only takes into consideration the current state + if !evm.chainRules.IsConstantinople { + // This checks for 3 scenario's and calculates gas accordingly: + // + // 1. From a zero-value address to a non-zero value (NEW VALUE) + // 2. From a non-zero value address to a zero-value address (DELETE) + // 3. From a non-zero to a non-zero (CHANGE) + switch { + case current == (common.Hash{}) && y.Sign() != 0: // 0 => non 0 + return params.SstoreSetGas, nil + case current != (common.Hash{}) && y.Sign() == 0: // non 0 => 0 + evm.StateDB.AddRefund(params.SstoreRefundGas) + return params.SstoreClearGas, nil + default: // non 0 => non 0 (or 0 => 0) + return params.SstoreResetGas, nil + } } + // The new gas metering is based on net gas costs (EIP-1283): + // + // 1. If current value equals new value (this is a no-op), 200 gas is deducted. + // 2. If current value does not equal new value + // 2.1. If original value equals current value (this storage slot has not been changed by the current execution context) + // 2.1.1. If original value is 0, 20000 gas is deducted. + // 2.1.2. Otherwise, 5000 gas is deducted. If new value is 0, add 15000 gas to refund counter. + // 2.2. If original value does not equal current value (this storage slot is dirty), 200 gas is deducted. Apply both of the following clauses. + // 2.2.1. If original value is not 0 + // 2.2.1.1. If current value is 0 (also means that new value is not 0), remove 15000 gas from refund counter. We can prove that refund counter will never go below 0. + // 2.2.1.2. If new value is 0 (also means that current value is not 0), add 15000 gas to refund counter. + // 2.2.2. If original value equals new value (this storage slot is reset) + // 2.2.2.1. If original value is 0, add 19800 gas to refund counter. + // 2.2.2.2. Otherwise, add 4800 gas to refund counter. + value := common.BigToHash(y) + if current == value { // noop (1) + return params.NetSstoreNoopGas, nil + } + original := evm.StateDB.GetCommittedState(contract.Address(), common.BigToHash(x)) + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return params.NetSstoreInitGas, nil + } + if value == (common.Hash{}) { // delete slot (2.1.2b) + evm.StateDB.AddRefund(params.NetSstoreClearRefund) + } + return params.NetSstoreCleanGas, nil // write existing slot (2.1.2) + } + if original != (common.Hash{}) { + if current == (common.Hash{}) { // recreate slot (2.2.1.1) + evm.StateDB.SubRefund(params.NetSstoreClearRefund) + } else if value == (common.Hash{}) { // delete slot (2.2.1.2) + evm.StateDB.AddRefund(params.NetSstoreClearRefund) + } + } + if original == value { + if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) + evm.StateDB.AddRefund(params.NetSstoreResetClearRefund) + } else { // reset to original existing slot (2.2.2.2) + evm.StateDB.AddRefund(params.NetSstoreResetRefund) + } + } + return params.NetSstoreDirtyGas, nil } func makeGasLog(n uint64) gasFunc { diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 191a50396..970a081ad 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -494,7 +494,7 @@ func BenchmarkOpMstore(bench *testing.B) { func BenchmarkOpSHA3(bench *testing.B) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(Context{}, nil, nil, params.TestChainConfig, Config{}) stack = newstack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) diff --git a/core/vm/interface.go b/core/vm/interface.go index 6267bf000..958ac52d2 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -26,6 +26,7 @@ import ( // Quorum uses a cut-down StateDB, MinimalApiState. We leave the methods in StateDB commented out so they'll produce a // conflict when upstream changes. +//TODO(Amal): to be reviewed type MinimalApiState interface { GetBalance(addr common.Address) *big.Int GetCode(addr common.Address) []byte diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index 45f262b30..fea16807b 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -50,7 +50,8 @@ func (*dummyStatedb) GetRefund() uint64 { return 1337 } func TestStoreCapture(t *testing.T) { var ( - env = NewEVM(Context{}, &dummyStatedb{}, nil, params.TestChainConfig, Config{}) + db = &dummyStatedb{} + env = NewEVM(Context{}, db, db, params.TestChainConfig, Config{}) logger = NewStructLogger(nil) mem = NewMemory() stack = newstack() diff --git a/eth/api_backend.go b/eth/api_backend.go index 72f763837..fbbe81ad9 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -288,7 +288,7 @@ func (s EthAPIState) GetNonce(addr common.Address) uint64 { return s.state.GetNonce(addr) } -// TODO: implement the following methods for Quorum +// TODO(Amal): implement the following methods for Quorum func (s EthAPIState) GetProof(common.Address) ([][]byte, error) { return nil, nil } diff --git a/eth/handler_test.go b/eth/handler_test.go index 529ee22da..5fe16bedc 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -562,7 +562,7 @@ func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) { if err != nil { t.Fatalf("failed to create new blockchain: %v", err) } - pm, err := NewProtocolManager(config, downloader.FullSync, DefaultConfig.NetworkId, evmux, new(testTxPool), pow, blockchain, db) + pm, err := NewProtocolManager(config, downloader.FullSync, DefaultConfig.NetworkId, evmux, new(testTxPool), pow, blockchain, db, false) if err != nil { t.Fatalf("failed to start test protocol manager: %v", err) } diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index e2cedb516..3fc6a4d5c 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -51,7 +51,8 @@ type dummyStatedb struct { func (*dummyStatedb) GetRefund() uint64 { return 1337 } func runTrace(tracer *Tracer) (json.RawMessage, error) { - env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, nil, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) + db := &dummyStatedb{} + env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, db, db, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) contract := vm.NewContract(account{}, account{}, big.NewInt(0), 10000) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} @@ -132,8 +133,8 @@ func TestHaltBetweenSteps(t *testing.T) { if err != nil { t.Fatal(err) } - - env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, nil, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) + db := &dummyStatedb{} + env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, db, db, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0) tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, contract, 0, nil) diff --git a/miner/worker_test.go b/miner/worker_test.go index 7c8f167a1..bda4f17c3 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -124,6 +124,7 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine } } +func (b *testWorkerBackend) ChainDb() ethdb.Database { return b.db } func (b *testWorkerBackend) BlockChain() *core.BlockChain { return b.chain } func (b *testWorkerBackend) TxPool() *core.TxPool { return b.txPool } func (b *testWorkerBackend) PostChainEvents(events []interface{}) { @@ -153,7 +154,7 @@ func testPendingStateAndBlock(t *testing.T, chainConfig *params.ChainConfig, eng // Ensure snapshot has been updated. time.Sleep(100 * time.Millisecond) - block, state := w.pending() + block, state, _ := w.pending() if block.NumberU64() != 1 { t.Errorf("block number mismatch: have %d, want %d", block.NumberU64(), 1) } @@ -164,7 +165,7 @@ func testPendingStateAndBlock(t *testing.T, chainConfig *params.ChainConfig, eng // Ensure the new tx events has been processed time.Sleep(100 * time.Millisecond) - block, state = w.pending() + block, state, _ = w.pending() if balance := state.GetBalance(testUserAddress); balance.Cmp(big.NewInt(2000)) != 0 { t.Errorf("account balance mismatch: have %d, want %d", balance, 2000) } diff --git a/node/config.go b/node/config.go index e54f3a9d8..8d045a167 100644 --- a/node/config.go +++ b/node/config.go @@ -368,9 +368,7 @@ func (c *Config) parsePersistentNodes(path string) []*enode.Node { if url == "" { continue } - log.Info("AJ-parsePersistentNodes1", "url", url) node, err := enode.ParseV4(url) - log.Info("AJ-parsePersistentNodes2", "url", url, "ID", node.ID().String(), "EnodeID", node.EnodeID()) if err != nil { log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err)) continue diff --git a/p2p/enode/idscheme.go b/p2p/enode/idscheme.go index 9b495fd4f..1d65c6e98 100644 --- a/p2p/enode/idscheme.go +++ b/p2p/enode/idscheme.go @@ -86,7 +86,8 @@ func (V4ID) NodeAddr(r *enr.Record) []byte { buf := make([]byte, 64) math.ReadBits(pubkey.X, buf[:32]) math.ReadBits(pubkey.Y, buf[32:]) - return crypto.Keccak256(buf) + b := crypto.Keccak256(buf) + return b } // Secp256k1 is the "secp256k1" key, which holds a public key. diff --git a/p2p/enode/node.go b/p2p/enode/node.go index 3ca1d54f2..dd46eac4a 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -82,6 +82,7 @@ func (n *Node) UDP() int { return int(port) } +//TODO: Amal to review // RAFTPORT returns the RAFT PORT of the node func (n *Node) RAFTPORT() int { var port enr.RAFTPORT @@ -194,6 +195,34 @@ func (n *ID) UnmarshalText(text []byte) error { return nil } +// ID is a unique identifier for each node used by RAFT +type EnodeID [64]byte + +// ID prints as a long hexadecimal number. +func (n EnodeID) String() string { + return fmt.Sprintf("%x", n[:]) +} + +// The Go syntax representation of a ID is a call to HexID. +func (n EnodeID) GoString() string { + return fmt.Sprintf("enode.HexID(\"%x\")", n[:]) +} + +// MarshalText implements the encoding.TextMarshaler interface. +func (n EnodeID) MarshalText() ([]byte, error) { + return []byte(hex.EncodeToString(n[:])), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +func (n *EnodeID) UnmarshalText(text []byte) error { + id, err := RaftHexID(string(text)) + if err != nil { + return err + } + *n = id + return nil +} + // HexID converts a hex string to an ID. // The string may be prefixed with 0x. // It panics if the string is not a valid ID. @@ -205,6 +234,20 @@ func HexID(in string) ID { return id } +//TODO(Amal): to review +func RaftHexID(in string) (EnodeID, error) { + var id EnodeID + b, err := hex.DecodeString(strings.TrimPrefix(in, "0x")) + if err != nil { + return id, err + } else if len(b) != len(id) { + return id, fmt.Errorf("wrong length, want %d hex chars", len(id)*2) + } + + copy(id[:], b) + return id, nil +} + func parseID(in string) (ID, error) { var id ID b, err := hex.DecodeString(strings.TrimPrefix(in, "0x")) diff --git a/p2p/enode/urlv4.go b/p2p/enode/urlv4.go index ecd6e2df3..04d052a0c 100644 --- a/p2p/enode/urlv4.go +++ b/p2p/enode/urlv4.go @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enr" ) @@ -93,7 +92,7 @@ func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp, raftPort int) *Node { } if raftPort != 0 { - r.Set(enr.RAFTPORT(tcp)) + r.Set(enr.RAFTPORT(raftPort)) } signV4Compat(&r, pubkey) @@ -101,7 +100,6 @@ func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp, raftPort int) *Node { if err != nil { panic(err) } - log.Info("NewV4", "raftPort", raftPort, "nodeId", n.ID().String(), "NodeId.bytes", n.ID().Bytes()) return n } @@ -122,11 +120,9 @@ func parseComplete(rawurl string) (*Node, error) { if u.User == nil { return nil, errors.New("does not contain node ID") } - log.Info("AJ-parseComplete1", "u.User", u.User.String()) if id, err = parsePubkey(u.User.String()); err != nil { return nil, fmt.Errorf("invalid node ID (%v)", err) } - log.Info("AJ-parseComplete2", "id", id) // Parse the IP address. host, port, err := net.SplitHostPort(u.Host) if err != nil { @@ -187,7 +183,7 @@ func parsePubkey(in string) (*ecdsa.PublicKey, error) { return crypto.UnmarshalPubkey(b) } -// TODO: Amal to review it - added for RAFT +// TODO(Amal): to review it - added for RAFT func (n *Node) EnodeID() string { var ( scheme enr.ID @@ -215,10 +211,8 @@ func (n *Node) v4URL() string { n.Load((*Secp256k1)(&key)) switch { case scheme == "v4" || key != ecdsa.PublicKey{}: - log.Info("AJ-schemeV4") nodeid = fmt.Sprintf("%x", crypto.FromECDSAPub(&key)[1:]) default: - log.Info("AJ-NOT schemeV4") nodeid = fmt.Sprintf("%s.%x", scheme, n.id[:]) } u := url.URL{Scheme: "enode"} diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go index 21671c2f3..444820c15 100644 --- a/p2p/enr/enr.go +++ b/p2p/enr/enr.go @@ -40,7 +40,6 @@ import ( "io" "sort" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" ) @@ -121,7 +120,6 @@ func (r *Record) Load(e Entry) error { if err := rlp.DecodeBytes(r.pairs[i].v, e); err != nil { return &KeyError{Key: e.ENRKey(), Err: err} } - log.Info("AJ-Load", "key", e.ENRKey(), "value", r.pairs[i].v) return nil } return &KeyError{Key: e.ENRKey(), Err: errNotFound} diff --git a/p2p/server.go b/p2p/server.go index f3d270334..38f73289d 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -925,8 +925,6 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro } else { c.node = nodeFromConn(remotePubkey, c.fd) } - log.Info("AJ-setupConn1", "remotePubkey", remotePubkey) - log.Info("AJ-setupConn2", "c.node.ID", c.node.ID(), "c.node.ID.Bytes", c.node.ID().Bytes(), "node", c.node.String()) clog := srv.log.New("id", c.node.ID(), "addr", c.fd.RemoteAddr(), "conn", c.flags) //START - QUORUM Permissioning diff --git a/params/config.go b/params/config.go index 19a3256e9..1c8e34cdd 100644 --- a/params/config.go +++ b/params/config.go @@ -131,16 +131,16 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil, nil, false} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, nil, false} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil, false} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil, false} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil, nil, false} + TestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, nil, false} TestRules = TestChainConfig.Rules(new(big.Int)) QuorumTestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, nil, common.Hash{}, nil, nil, nil, nil, nil, new(EthashConfig), nil, nil, true} diff --git a/raft/api.go b/raft/api.go index 1a699bb6c..55c652571 100755 --- a/raft/api.go +++ b/raft/api.go @@ -31,6 +31,7 @@ func (s *PublicRaftAPI) RemovePeer(raftId uint16) { } func (s *PublicRaftAPI) Leader() (string, error) { + addr, err := s.raftService.raftProtocolManager.LeaderAddress() if nil != err { return "", err diff --git a/raft/handler.go b/raft/handler.go index b31eb902a..c8fb14e70 100755 --- a/raft/handler.go +++ b/raft/handler.go @@ -23,17 +23,16 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rlp" - "crypto/ecdsa" "github.com/coreos/etcd/etcdserver/stats" raftTypes "github.com/coreos/etcd/pkg/types" etcdRaft "github.com/coreos/etcd/raft" "github.com/coreos/etcd/raft/raftpb" "github.com/coreos/etcd/rafthttp" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/syndtr/goleveldb/leveldb" - "gopkg.in/fatih/set.v0" + + "github.com/deckarep/golang-set" ) type ProtocolManager struct { @@ -56,7 +55,7 @@ type ProtocolManager struct { // Remote peer state (protected by mu vs concurrent access via JS) leader uint16 peers map[uint16]*Peer - removedPeers *set.Set // *Permanently removed* peers + removedPeers mapset.Set // *Permanently removed* peers // P2P transport p2pServer *p2p.Server // Initialized in start() @@ -107,7 +106,7 @@ func NewProtocolManager(raftId uint16, raftPort uint16, blockchain *core.BlockCh bootstrapNodes: bootstrapNodes, peers: make(map[uint16]*Peer), leader: uint16(etcdRaft.None), - removedPeers: set.New(set.ThreadSafe).(*set.Set), + removedPeers: mapset.NewSet(), joinExisting: joinExisting, blockchain: blockchain, eventMux: mux, @@ -201,10 +200,12 @@ func (pm *ProtocolManager) NodeInfo() *RaftNodeInfo { peerIdx += 1 } - removedPeerIfaces := pm.removedPeers.List() - removedPeerIds := make([]uint16, len(removedPeerIfaces)) - for i, removedIface := range removedPeerIfaces { + removedPeerIfaces := pm.removedPeers + removedPeerIds := make([]uint16, removedPeerIfaces.Cardinality()) + i := 0 + for removedIface := range removedPeerIfaces.Iterator().C { removedPeerIds[i] = removedIface.(uint16) + i++ } // @@ -247,8 +248,8 @@ func (pm *ProtocolManager) nextRaftId() uint16 { } } - removedPeerIfaces := pm.removedPeers.List() - for _, removedIface := range removedPeerIfaces { + removedPeerIfaces := pm.removedPeers + for removedIface := range removedPeerIfaces.Iterator().C { removedId := removedIface.(uint16) if maxId < removedId { @@ -263,7 +264,7 @@ func (pm *ProtocolManager) isRaftIdRemoved(id uint16) bool { pm.mu.RLock() defer pm.mu.RUnlock() - return pm.removedPeers.Has(id) + return pm.removedPeers.Contains(id) } func (pm *ProtocolManager) isRaftIdUsed(raftId uint16) bool { @@ -286,14 +287,14 @@ func (pm *ProtocolManager) isNodeAlreadyInCluster(node *enode.Node) error { peerNode := peer.p2pNode if peerNode.ID() == node.ID() { - return fmt.Errorf("node with this enode has already been added to the cluster: %v", node.ID) + return fmt.Errorf("node with this enode has already been added to the cluster: %s", node.ID()) } if peerNode.IP().Equal(node.IP()) { if peerNode.TCP() == node.TCP() { - return fmt.Errorf("existing node %v with raft ID %v is already using eth p2p at %v:%v", peerNode.ID, peerRaftId, node.IP, node.TCP) + return fmt.Errorf("existing node %v with raft ID %v is already using eth p2p at %v:%v", peerNode.ID(), peerRaftId, node.IP(), node.TCP()) } else if peer.address.RaftPort == enr.RAFTPORT(node.RAFTPORT()) { - return fmt.Errorf("existing node %v with raft ID %v is already using raft at %v:%v", peerNode.ID, peerRaftId, node.IP, node.RAFTPORT()) + return fmt.Errorf("existing node %v with raft ID %v is already using raft at %v:%v", peerNode.ID(), peerRaftId, node.IP(), node.RAFTPORT()) } } } @@ -641,31 +642,21 @@ func raftUrl(address *Address) string { return fmt.Sprintf("http://%s:%d", address.Ip, address.RaftPort) } -func decodePubkey64(b []byte) (*ecdsa.PublicKey, error) { - return crypto.UnmarshalPubkey(append([]byte{0x04}, b...)) -} - func (pm *ProtocolManager) addPeer(address *Address) { pm.mu.Lock() defer pm.mu.Unlock() raftId := address.RaftId - log.Info("AJ-addPeer:", "raftId", raftId, "nodeId", address.NodeId.String(), "bytes", address.NodeId.Bytes(), "address", address) - - //TODO: Amal to confirm if this decoding is correct - //pubKey, err := decodePubkey64(address.NodeId.Bytes()) - - /*if err != nil { - log.Error("AJ-error decoding pub key", "nodeId", address.NodeId.String(), "err", err) - return - }*/ - - log.Info("addPeer: decoded pub key", "pubKey", address.PubKey) + //TODO(Amal): to review + pubKey, err := enode.HexPubkey(address.NodeId.String()) + if err != nil { + log.Error("error decoding pub key from enodeId", "enodeId", address.NodeId.String(), "err", err) + panic(err) + } // Add P2P connection: - p2pNode := enode.NewV4(address.PubKey, address.Ip, 0, int(address.P2pPort), 0) - log.Info("AJ-p2pNode", "ID1", address.NodeId.String(), "ID2", p2pNode.ID().String()) + p2pNode := enode.NewV4(pubKey, address.Ip, 0, int(address.P2pPort), int(address.RaftPort)) pm.p2pServer.AddPeer(p2pNode) // Add raft transport connection: @@ -858,7 +849,6 @@ func (pm *ProtocolManager) makeInitialRaftPeers() (raftPeers []etcdRaft.Peer, pe // requiring the use of static peers for the initial set, and load them from e.g. another JSON file which // contains pairs of enodes and raft ports, or we can get this initial peer list from commandline flags. address := newAddress(raftId, node.RAFTPORT(), node) - raftPeers[i] = etcdRaft.Peer{ ID: uint64(raftId), Context: address.toBytes(), diff --git a/raft/peer.go b/raft/peer.go index b13268745..e6b486acd 100644 --- a/raft/peer.go +++ b/raft/peer.go @@ -5,38 +5,35 @@ import ( "net" "fmt" - "log" - - "crypto/ecdsa" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/rlp" + "log" ) -const nodeIDBits = 512 - -type EnodeID [nodeIDBits / 8]byte - // Serializable information about a Peer. Sufficient to build `etcdRaft.Peer` // or `discover.Node`. type Address struct { - RaftId uint16 `json:"raftId"` - NodeId [64]byte `json:"nodeId"` - Ip net.IP `json:"ip"` - P2pPort enr.TCP `json:"p2pPort"` - RaftPort enr.RAFTPORT `json:"raftPort"` - PubKey *ecdsa.PublicKey + RaftId uint16 `json:"raftId"` + NodeId enode.EnodeID `json:"nodeId"` + Ip net.IP `json:"ip"` + P2pPort enr.TCP `json:"p2pPort"` + RaftPort enr.RAFTPORT `json:"raftPort"` } +// TODO(Amal): to review + func newAddress(raftId uint16, raftPort int, node *enode.Node) *Address { - node.ID().Bytes() + id, err := enode.RaftHexID(node.EnodeID()) + if err != nil { + panic(err) + } return &Address{ RaftId: raftId, - NodeId: []byte(node.EnodeID()), + NodeId: id, Ip: node.IP(), P2pPort: enr.TCP(node.TCP()), RaftPort: enr.RAFTPORT(raftPort), - PubKey: node.Pubkey(), } } @@ -54,7 +51,7 @@ func (addr *Address) DecodeRLP(s *rlp.Stream) error { // These fields need to be public: var temp struct { RaftId uint16 - NodeId enode.ID + NodeId enode.EnodeID Ip net.IP P2pPort enr.TCP RaftPort enr.RAFTPORT diff --git a/raft/snapshot.go b/raft/snapshot.go index 5ce04ec4c..7536453e2 100644 --- a/raft/snapshot.go +++ b/raft/snapshot.go @@ -10,12 +10,12 @@ import ( "github.com/coreos/etcd/raft/raftpb" "github.com/coreos/etcd/snap" "github.com/coreos/etcd/wal/walpb" + "github.com/deckarep/golang-set" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" - "gopkg.in/fatih/set.v0" ) // Snapshot @@ -37,9 +37,7 @@ func (pm *ProtocolManager) buildSnapshot() *Snapshot { defer pm.mu.RUnlock() numNodes := len(pm.confState.Nodes) - log.Info("AJ-numNodes", "nodesSize", numNodes, "nodes", pm.confState.Nodes) - log.Info("AJ-peers", "pm.peers", pm.peers) - numRemovedNodes := pm.removedPeers.Size() + numRemovedNodes := pm.removedPeers.Cardinality() snapshot := &Snapshot{ addresses: make([]Address, numNodes), @@ -61,9 +59,10 @@ func (pm *ProtocolManager) buildSnapshot() *Snapshot { sort.Sort(ByRaftId(snapshot.addresses)) // Populate removed IDs - - for i, removedIface := range pm.removedPeers.List() { + i := 0 + for removedIface := range pm.removedPeers.Iterator().C { snapshot.removedRaftIds[i] = removedIface.(uint16) + i++ } return snapshot @@ -101,8 +100,8 @@ func (pm *ProtocolManager) triggerSnapshot(index uint64) { pm.mu.Unlock() } -func confStateIdSet(confState raftpb.ConfState) *set.Set { - set := set.New(set.ThreadSafe).(*set.Set) +func confStateIdSet(confState raftpb.ConfState) mapset.Set { + set := mapset.NewSet() for _, rawRaftId := range confState.Nodes { set.Add(uint16(rawRaftId)) } @@ -117,7 +116,7 @@ func (pm *ProtocolManager) updateClusterMembership(newConfState raftpb.ConfState // Update tombstones for permanently removed peers. For simplicity we do not // allow the re-use of peer IDs once a peer is removed. - removedPeers := set.New(set.ThreadSafe).(*set.Set) + removedPeers := mapset.NewSet() for _, removedRaftId := range removedRaftIds { removedPeers.Add(removedRaftId) } @@ -129,8 +128,8 @@ func (pm *ProtocolManager) updateClusterMembership(newConfState raftpb.ConfState prevIds := confStateIdSet(prevConfState) newIds := confStateIdSet(newConfState) - idsToRemove := set.Difference(prevIds, newIds) - for _, idIfaceToRemove := range idsToRemove.List() { + idsToRemove := prevIds.Difference(newIds) + for idIfaceToRemove := range idsToRemove.Iterator().C { raftId := idIfaceToRemove.(uint16) log.Info("removing old raft peer", "peer id", raftId) diff --git a/raft/speculative_chain.go b/raft/speculative_chain.go index 51646dc36..5dcb82658 100644 --- a/raft/speculative_chain.go +++ b/raft/speculative_chain.go @@ -5,8 +5,8 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" - "gopkg.in/fatih/set.v0" - lane "gopkg.in/oleiade/lane.v1" + "github.com/deckarep/golang-set" + "gopkg.in/oleiade/lane.v1" ) // The speculative chain represents blocks that we have minted which haven't been accepted into the chain yet, building @@ -21,16 +21,16 @@ import ( type speculativeChain struct { head *types.Block unappliedBlocks *lane.Deque - expectedInvalidBlockHashes *set.Set // This is thread-safe. This set is referred to as our "guard" below. - proposedTxes *set.Set // This is thread-safe. + expectedInvalidBlockHashes mapset.Set // This is thread-safe. This set is referred to as our "guard" below. + proposedTxes mapset.Set // This is thread-safe. } func newSpeculativeChain() *speculativeChain { return &speculativeChain{ head: nil, unappliedBlocks: lane.NewDeque(), - expectedInvalidBlockHashes: set.New(set.ThreadSafe).(*set.Set), - proposedTxes: set.New(set.ThreadSafe).(*set.Set), + expectedInvalidBlockHashes: mapset.NewSet(), + proposedTxes: mapset.NewSet(), } } @@ -87,7 +87,7 @@ func (chain *speculativeChain) unwindFrom(invalidHash common.Hash, headBlock *ty // check our "guard" to see if this is a (descendant) block we're // expected to be ruled invalid. if we find it, remove from the guard - if chain.expectedInvalidBlockHashes.Has(invalidHash) { + if chain.expectedInvalidBlockHashes.Contains(invalidHash) { log.Info("Removing expected-invalid block from guard.", "block", invalidHash) chain.expectedInvalidBlockHashes.Remove(invalidHash) @@ -140,7 +140,9 @@ func (chain *speculativeChain) recordProposedTransactions(txes types.Transaction for i, tx := range txes { txHashIs[i] = tx.Hash() } - chain.proposedTxes.Add(txHashIs...) + for _, i := range txHashIs { + chain.proposedTxes.Add(i) + } } // Removes txes in block from our "blacklist" of "proposed tx" hashes. When we @@ -161,7 +163,9 @@ func (chain *speculativeChain) removeProposedTxes(block *types.Block) { // here and in mintNewBlock concurrently. using a finer-grained set-specific // lock here is preferable, because mintNewBlock holds its locks for a // nontrivial amount of time. - chain.proposedTxes.Remove(minedTxInterfaces...) + for _, i := range minedTxInterfaces { + chain.proposedTxes.Remove(i) + } } func (chain *speculativeChain) withoutProposedTxes(addrTxes AddressTxes) AddressTxes { @@ -170,7 +174,7 @@ func (chain *speculativeChain) withoutProposedTxes(addrTxes AddressTxes) Address for addr, txes := range addrTxes { filteredTxes := make(types.Transactions, 0) for _, tx := range txes { - if !chain.proposedTxes.Has(tx.Hash()) { + if !chain.proposedTxes.Contains(tx.Hash()) { filteredTxes = append(filteredTxes, tx) } }