Merge pull request #2172 from Daniel-VDM/blacklist

Add a blacklist
pull/2194/head
Daniel Van Der Maden 5 years ago committed by GitHub
commit b194241bc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 0
      .hmy/blacklist.txt
  2. 20
      BLACKLIST.md
  3. 2
      cmd/client/txgen/main.go
  4. 2
      cmd/client/wallet/main.go
  5. 2
      cmd/client/wallet_stress_test/main.go
  6. 31
      cmd/harmony/main.go
  7. 55
      core/tx_pool.go
  8. 45
      core/tx_pool_test.go
  9. 6
      node/node.go
  10. 4
      node/node_handler_test.go
  11. 6
      node/node_test.go
  12. 9
      scripts/node.sh

@ -0,0 +1,20 @@
# Blacklist info
The black list is a newline delimited file of wallet addresses. It can also support comments with the `#` character.
## Default Location
By default, the harmony binary looks for the file `./.hmy/blaklist.txt`.
## Example File
```
one1spshr72utf6rwxseaz339j09ed8p6f8ke370zj
one1uyshu2jgv8w465yc8kkny36thlt2wvel89tcmg # This is a comment
one1r4zyyjqrulf935a479sgqlpa78kz7zlcg2jfen
```
## Details
Each transaction added to the tx-pool has its `to` and `from` address checked against this blacklist.
If there is a hit, the transaction is considered invalid and is dropped from the tx-pool.

@ -98,7 +98,7 @@ func setUpTXGen() *node.Node {
decider := quorum.NewDecider(quorum.SuperMajorityVote)
consensusObj, err := consensus.New(myhost, uint32(shardID), p2p.Peer{}, nil, decider)
chainDBFactory := &shardchain.MemDBFactory{}
txGen := node.New(myhost, consensusObj, chainDBFactory, false) //Changed it : no longer archival node.
txGen := node.New(myhost, consensusObj, chainDBFactory, nil, false) //Changed it : no longer archival node.
txGen.Client = client.NewClient(txGen.GetHost(), uint32(shardID))
consensusObj.ChainReader = txGen.Blockchain()
genesisShardingConfig := shard.Schedule.InstanceForEpoch(big.NewInt(core.GenesisEpoch))

@ -306,7 +306,7 @@ func createWalletNode() *node.Node {
utils.FatalErrMsg(err, "cannot initialize network")
}
chainDBFactory := &shardchain.MemDBFactory{}
w := node.New(host, nil, chainDBFactory, false)
w := node.New(host, nil, chainDBFactory, nil, false)
w.Client = client.NewClient(w.GetHost(), uint32(shardID))
w.NodeConfig.SetRole(nodeconfig.ClientNode)

@ -189,7 +189,7 @@ func createWalletNode() *node.Node {
utils.FatalErrMsg(err, "cannot initialize network")
}
chainDBFactory := &shardchain.MemDBFactory{}
w := node.New(host, nil, chainDBFactory, false)
w := node.New(host, nil, chainDBFactory, nil, false)
w.Client = client.NewClient(w.GetHost(), uint32(shardID))
w.NodeConfig.SetRole(nodeconfig.ClientNode)

@ -4,12 +4,14 @@ import (
"encoding/hex"
"flag"
"fmt"
"io/ioutil"
"math/big"
"math/rand"
"os"
"path"
"runtime"
"strconv"
"strings"
"time"
ethCommon "github.com/ethereum/go-ethereum/common"
@ -127,6 +129,8 @@ var (
doRevertBefore = flag.Int("do_revert_before", 0, "If the current block is less than do_revert_before, revert all blocks until (including) revert_to block")
revertTo = flag.Int("revert_to", 0, "The revert will rollback all blocks until and including block number revert_to")
revertBeacon = flag.Bool("revert_beacon", false, "Whether to revert beacon chain or the chain this node is assigned to")
// Blacklist of addresses
blacklistPath = flag.String("blacklist", "./.hmy/blacklist.txt", "Path to newline delimited file of blacklisted wallet addresses")
)
func initSetup() {
@ -322,9 +326,14 @@ func setupConsensusAndNode(nodeConfig *nodeconfig.ConfigType) *node.Node {
currentConsensus.DisableViewChangeForTestingOnly()
}
blacklist, err := setupBlacklist()
if err != nil {
utils.Logger().Warn().Msgf("Blacklist setup error: %s", err.Error())
}
// Current node.
chainDBFactory := &shardchain.LDBFactory{RootDir: nodeConfig.DBDir}
currentNode := node.New(myHost, currentConsensus, chainDBFactory, *isArchival)
currentNode := node.New(myHost, currentConsensus, chainDBFactory, blacklist, *isArchival)
switch {
case *networkType == nodeconfig.Localnet:
@ -416,6 +425,26 @@ func setupConsensusAndNode(nodeConfig *nodeconfig.ConfigType) *node.Node {
return currentNode
}
func setupBlacklist() (*map[ethCommon.Address]struct{}, error) {
utils.Logger().Debug().Msgf("Using blacklist file at `%s`", *blacklistPath)
dat, err := ioutil.ReadFile(*blacklistPath)
if err != nil {
return nil, err
}
addrMap := make(map[ethCommon.Address]struct{})
for _, line := range strings.Split(string(dat), "\n") {
if len(line) != 0 { // blacklist file may have trailing empty string line
b32 := strings.TrimSpace(strings.Split(string(line), "#")[0])
addr, err := common.Bech32ToAddress(b32)
if err != nil {
return nil, err
}
addrMap[addr] = struct{}{}
}
}
return &addrMap, nil
}
func main() {
// HACK Force usage of go implementation rather than the C based one. Do the right way, see the
// notes one line 66,67 of https://golang.org/src/net/net.go that say can make the decision at

@ -35,6 +35,7 @@ import (
"github.com/harmony-one/harmony/block"
"github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/types"
hmyCommon "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/internal/utils"
)
@ -83,6 +84,12 @@ var (
// ErrKnownTransaction is returned if a transaction that is already in the pool
// attempting to be added to the pool.
ErrKnownTransaction = errors.New("known transaction")
// ErrBlacklistFrom is returned if a transaction's from/source address is blacklisted
ErrBlacklistFrom = errors.New("`from` address of transaction in blacklist")
// ErrBlacklistTo is returned if a transaction's to/destination address is blacklisted
ErrBlacklistTo = errors.New("`to` address of transaction in blacklist")
)
var (
@ -145,6 +152,8 @@ type TxPoolConfig struct {
GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts
Lifetime time.Duration // Maximum amount of time non-executable transaction are queued
Blacklist *map[common.Address]struct{} // Set of accounts that cannot be a part of any transaction
}
// DefaultTxPoolConfig contains the default configurations for the transaction
@ -162,6 +171,8 @@ var DefaultTxPoolConfig = TxPoolConfig{
GlobalQueue: 1024,
Lifetime: 3 * time.Hour,
Blacklist: &map[common.Address]struct{}{},
}
// sanitize checks the provided user configurations and changes anything that's
@ -189,6 +200,11 @@ func (config *TxPoolConfig) sanitize() TxPoolConfig {
Msg("Sanitizing invalid txpool price bump")
conf.PriceBump = DefaultTxPoolConfig.PriceBump
}
if conf.Blacklist == nil {
utils.Logger().Warn().Msg("Sanitizing nil blacklist set")
conf.Blacklist = DefaultTxPoolConfig.Blacklist
}
return conf
}
@ -622,7 +638,26 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// Make sure the transaction is signed properly
from, err := types.Sender(pool.signer, tx)
if err != nil {
return errors.WithMessagef(ErrInvalidSender, "transaction sender is %v", from)
if b32, err := hmyCommon.AddressToBech32(from); err == nil {
return errors.WithMessagef(ErrInvalidSender, "transaction sender is %s", b32)
}
return ErrInvalidSender
}
// Make sure transaction does not have blacklisted addresses
if _, exists := (*pool.config.Blacklist)[from]; exists {
if b32, err := hmyCommon.AddressToBech32(from); err == nil {
return errors.WithMessagef(ErrBlacklistFrom, "transaction sender is %s", b32)
}
return ErrBlacklistFrom
}
// Make sure transaction does not burn funds by sending funds to blacklisted address
if tx.To() != nil {
if _, exists := (*pool.config.Blacklist)[*tx.To()]; exists {
if b32, err := hmyCommon.AddressToBech32(*tx.To()); err == nil {
return errors.WithMessagef(ErrBlacklistTo, "transaction receiver is %s", b32)
}
return ErrBlacklistTo
}
}
// Drop non-local transactions under our own minimal accepted gas price
local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network
@ -839,14 +874,14 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
// the sender as a local one in the mean time, ensuring it goes around the local
// pricing constraints.
func (pool *TxPool) AddLocal(tx *types.Transaction) error {
return errors.Cause(pool.addTx(tx, !pool.config.NoLocals))
return pool.addTx(tx, !pool.config.NoLocals)
}
// AddRemote enqueues a single transaction into the pool if it is valid. If the
// sender is not among the locally tracked ones, full pricing constraints will
// apply.
func (pool *TxPool) AddRemote(tx *types.Transaction) error {
return errors.Cause(pool.addTx(tx, false))
return pool.addTx(tx, false)
}
// AddLocals enqueues a batch of transactions into the pool if they are valid,
@ -871,10 +906,11 @@ func (pool *TxPool) addTx(tx *types.Transaction, local bool) error {
// Try to inject the transaction and update any state
replace, err := pool.add(tx, local)
if err != nil {
if errors.Cause(err) != ErrKnownTransaction {
errCause := errors.Cause(err)
if errCause != ErrKnownTransaction {
pool.txnErrorSink([]types.RPCTransactionError{*types.NewRPCTransactionError(tx.Hash(), err)})
}
return err
return errCause
}
// If we added a new transaction, run promotion checks and return
if !replace {
@ -901,14 +937,15 @@ func (pool *TxPool) addTxsLocked(txs []*types.Transaction, local bool) []error {
erroredTxns := []types.RPCTransactionError{}
for i, tx := range txs {
var replace bool
if replace, errs[i] = pool.add(tx, local); errs[i] == nil && !replace {
replace, err := pool.add(tx, local)
if err == nil && !replace {
from, _ := types.Sender(pool.signer, tx) // already validated
dirty[from] = struct{}{}
}
if errs[i] != nil && errors.Cause(errs[i]) != ErrKnownTransaction {
erroredTxns = append(erroredTxns, *types.NewRPCTransactionError(tx.Hash(), errs[i]))
if err != nil && err != ErrKnownTransaction {
erroredTxns = append(erroredTxns, *types.NewRPCTransactionError(tx.Hash(), err))
}
errs[i] = errors.Cause(err)
}
// Only reprocess the internal state if something was actually added
if len(dirty) > 0 {

@ -261,6 +261,51 @@ func TestInvalidTransactions(t *testing.T) {
}
}
func TestBlacklistedTransactions(t *testing.T) {
// DO NOT parallelize, test will add accounts to tx pool config.
// Create the pool
pool, _ := setupTxPool()
defer pool.Stop()
// Create testing keys
bannedFromKey, _ := crypto.GenerateKey()
goodFromKey, _ := crypto.GenerateKey()
// Create testing transactions
badTx := transaction(0, 25000, bannedFromKey)
goodTx := transaction(0, 25000, goodFromKey)
bannedFromAcc, _ := deriveSender(badTx)
bannedToAcc := *badTx.To()
goodFromAcc, _ := deriveSender(goodTx)
// Fund from accounts
pool.currentState.AddBalance(bannedFromAcc, big.NewInt(50100))
pool.currentState.AddBalance(goodFromAcc, big.NewInt(50100))
(*DefaultTxPoolConfig.Blacklist)[bannedToAcc] = struct{}{}
err := pool.AddRemotes([]*types.Transaction{badTx})
if err[0] != ErrBlacklistTo {
t.Error("expected", ErrBlacklistTo, "got", err[0])
}
delete(*DefaultTxPoolConfig.Blacklist, bannedToAcc)
(*DefaultTxPoolConfig.Blacklist)[bannedFromAcc] = struct{}{}
err = pool.AddRemotes([]*types.Transaction{badTx})
if err[0] != ErrBlacklistFrom {
t.Error("expected", ErrBlacklistFrom, "got", err[0])
}
// to acc is same for bad and good tx, so keep off blacklist for valid tx check
err = pool.AddRemotes([]*types.Transaction{goodTx})
if err[0] != nil {
t.Error("expected", nil, "got", err[0])
}
// cleanup blacklist config for other tests
DefaultTxPoolConfig.Blacklist = &map[common.Address]struct{}{}
}
func TestTransactionQueue(t *testing.T) {
t.Parallel()

@ -449,7 +449,7 @@ func (node *Node) GetSyncID() [SyncIDLength]byte {
// New creates a new node.
func New(host p2p.Host, consensusObj *consensus.Consensus,
chainDBFactory shardchain.DBFactory, isArchival bool) *Node {
chainDBFactory shardchain.DBFactory, blacklist *map[common.Address]struct{}, isArchival bool) *Node {
node := Node{}
const sinkSize = 4096
node.errorSink = struct {
@ -502,7 +502,9 @@ func New(host p2p.Host, consensusObj *consensus.Consensus,
node.BlockChannel = make(chan *types.Block)
node.ConfirmedBlockChannel = make(chan *types.Block)
node.BeaconBlockChannel = make(chan *types.Block)
node.TxPool = core.NewTxPool(core.DefaultTxPoolConfig, node.Blockchain().Config(), blockchain,
txPoolConfig := core.DefaultTxPoolConfig
txPoolConfig.Blacklist = blacklist
node.TxPool = core.NewTxPool(txPoolConfig, node.Blockchain().Config(), blockchain,
func(payload []types.RPCTransactionError) {
if len(payload) > 0 {
node.errorSink.Lock()

@ -33,7 +33,7 @@ func TestAddNewBlock(t *testing.T) {
t.Fatalf("Cannot craeate consensus: %v", err)
}
nodeconfig.SetNetworkType(nodeconfig.Devnet)
node := New(host, consensus, testDBFactory, false)
node := New(host, consensus, testDBFactory, nil, false)
txs := make(map[common.Address]types.Transactions)
stks := staking.StakingTransactions{}
@ -69,7 +69,7 @@ func TestVerifyNewBlock(t *testing.T) {
if err != nil {
t.Fatalf("Cannot craeate consensus: %v", err)
}
node := New(host, consensus, testDBFactory, false)
node := New(host, consensus, testDBFactory, nil, false)
txs := make(map[common.Address]types.Transactions)
stks := staking.StakingTransactions{}

@ -40,7 +40,7 @@ func TestNewNode(t *testing.T) {
if err != nil {
t.Fatalf("Cannot craeate consensus: %v", err)
}
node := New(host, consensus, testDBFactory, false)
node := New(host, consensus, testDBFactory, nil, false)
if node.Consensus == nil {
t.Error("Consensus is not initialized for the node")
}
@ -209,7 +209,7 @@ func TestAddPeers(t *testing.T) {
}
dRand := drand.New(host, 0, []p2p.Peer{leader, validator}, leader, nil, nil)
node := New(host, consensus, testDBFactory, false)
node := New(host, consensus, testDBFactory, nil, false)
node.DRand = dRand
r1 := node.AddPeers(peers1)
e1 := 2
@ -259,7 +259,7 @@ func TestAddBeaconPeer(t *testing.T) {
}
dRand := drand.New(host, 0, []p2p.Peer{leader, validator}, leader, nil, nil)
node := New(host, consensus, testDBFactory, false)
node := New(host, consensus, testDBFactory, nil, false)
node.DRand = dRand
for _, p := range peers1 {
ret := node.AddBeaconPeer(p)

@ -123,6 +123,7 @@ options:
-y run in legacy, foundational-node mode (default)
-M support multi-key mode (default: off)
-A enable archival node mode (default: off)
-B blacklist specify file containing blacklisted accounts as a newline delimited file (default: ./.hmy/blacklist.txt)
examples:
@ -159,7 +160,7 @@ BUCKET=pub.harmony.one
OS=$(uname -s)
unset start_clean loop run_as_root blspass do_not_download download_only metrics network node_type shard_id download_harmony_db db_file_to_dl
unset upgrade_rel public_rpc staking_mode pub_port multi_key
unset upgrade_rel public_rpc staking_mode pub_port multi_key blacklist
start_clean=false
loop=true
run_as_root=true
@ -174,11 +175,13 @@ public_rpc=false
staking_mode=false
multi_key=false
archival=false
blacklist=./.hmy/blacklist.txt
archival=false
${BLSKEYFILE=}
unset OPTIND OPTARG opt
OPTIND=1
while getopts :1chk:sSp:dDmN:tT:i:ba:U:PvVyzn:MA opt
while getopts :1chk:sSp:dDmN:tT:i:ba:U:PvVyzn:MAB: opt
do
case "${opt}" in
'?') usage "unrecognized option -${OPTARG}";;
@ -203,6 +206,7 @@ do
a) db_file_to_dl="${OPTARG}";;
U) upgrade_rel="${OPTARG}";;
P) public_rpc=true;;
B) blacklist="${OPTARG}";;
v) msg "version: $version"
exit 0 ;;
V) LD_LIBRARY_PATH=. ./harmony -version
@ -660,6 +664,7 @@ do
-is_genesis
-network_type="${network_type}"
-dns_zone="${dns_zone}"
-blacklist="${blacklist}"
)
args+=(
-is_archival="${archival}"

Loading…
Cancel
Save