Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[txpool] fix #4215, support allowed transaction list. #4218

Merged
merged 10 commits into from Jul 13, 2022
7 changes: 7 additions & 0 deletions cmd/harmony/config_migrations.go
Expand Up @@ -259,5 +259,12 @@ func init() {
confTree.Set("Version", "2.5.3")
return confTree
}
migrations["2.5.3"] = func(confTree *toml.Tree) *toml.Tree {
if confTree.Get("TxPool.AllowedTxsFile") == nil {
confTree.Set("TxPool.AllowedTxsFile", defaultConfig.TxPool.AllowedTxsFile)
}
confTree.Set("Version", "2.5.4")
return confTree
}

}
1 change: 1 addition & 0 deletions cmd/harmony/config_migrations_test.go
Expand Up @@ -313,6 +313,7 @@ Version = "1.0.4"
[TxPool]
BlacklistFile = "./.hmy/blacklist.txt"
LocalAccountsFile = "./.hmy/locals.txt"
AllowedTxsFile = "./.hmy/allowedtxs.txt"

[WS]
Enabled = true
Expand Down
1 change: 1 addition & 0 deletions cmd/harmony/config_test.go
Expand Up @@ -84,6 +84,7 @@ Version = "1.0.4"
[TxPool]
BlacklistFile = "./.hmy/blacklist.txt"
LocalAccountsFile = "./.hmy/locals.txt"
AllowedTxsFile = "./.hmy/allowedtxs.txt"

[Sync]
Downloader = false
Expand Down
3 changes: 2 additions & 1 deletion cmd/harmony/default.go
Expand Up @@ -5,7 +5,7 @@ import (
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
)

const tomlConfigVersion = "2.5.3" // bump from 2.5.2 for rpc filters
const tomlConfigVersion = "2.5.4" // bump from 2.5.2 for rpc filters

const (
defNetworkType = nodeconfig.Mainnet
Expand Down Expand Up @@ -70,6 +70,7 @@ var defaultConfig = harmonyconfig.HarmonyConfig{
},
TxPool: harmonyconfig.TxPoolConfig{
BlacklistFile: "./.hmy/blacklist.txt",
AllowedTxsFile: "./.hmy/allowedtxs.txt",
RosettaFixFile: "",
AccountSlots: 16,
LocalAccountsFile: "./.hmy/locals.txt",
Expand Down
9 changes: 9 additions & 0 deletions cmd/harmony/flags.go
Expand Up @@ -135,6 +135,7 @@ var (
tpBlacklistFileFlag,
legacyTPBlacklistFileFlag,
localAccountsFileFlag,
allowedTxsFileFlag,
}

pprofFlags = []cli.Flag{
Expand Down Expand Up @@ -1080,6 +1081,11 @@ var (
Usage: "file of local wallet addresses",
DefValue: defaultConfig.TxPool.LocalAccountsFile,
}
allowedTxsFileFlag = cli.StringFlag{
Name: "txpool.allowedtxs",
Usage: "file of allowed transactions",
DefValue: defaultConfig.TxPool.AllowedTxsFile,
}
)

func applyTxPoolFlags(cmd *cobra.Command, config *harmonyconfig.HarmonyConfig) {
Expand All @@ -1101,6 +1107,9 @@ func applyTxPoolFlags(cmd *cobra.Command, config *harmonyconfig.HarmonyConfig) {
if cli.IsFlagChanged(cmd, localAccountsFileFlag) {
config.TxPool.LocalAccountsFile = cli.GetStringFlagValue(cmd, localAccountsFileFlag)
}
if cli.IsFlagChanged(cmd, allowedTxsFileFlag) {
config.TxPool.AllowedTxsFile = cli.GetStringFlagValue(cmd, allowedTxsFileFlag)
}
}

// pprof flags
Expand Down
8 changes: 7 additions & 1 deletion cmd/harmony/flags_test.go
Expand Up @@ -106,6 +106,7 @@ func TestHarmonyFlags(t *testing.T) {
},
TxPool: harmonyconfig.TxPoolConfig{
BlacklistFile: "./.hmy/blacklist.txt",
AllowedTxsFile: "./.hmy/allowedtxs.txt",
RosettaFixFile: "",
AccountSlots: 16,
LocalAccountsFile: "./.hmy/locals.txt",
Expand Down Expand Up @@ -875,15 +876,17 @@ func TestTxPoolFlags(t *testing.T) {
args: []string{},
expConfig: harmonyconfig.TxPoolConfig{
BlacklistFile: defaultConfig.TxPool.BlacklistFile,
AllowedTxsFile: defaultConfig.TxPool.AllowedTxsFile,
RosettaFixFile: defaultConfig.TxPool.RosettaFixFile,
AccountSlots: defaultConfig.TxPool.AccountSlots,
LocalAccountsFile: defaultConfig.TxPool.LocalAccountsFile,
},
},
{
args: []string{"--txpool.blacklist", "blacklist.file", "--txpool.rosettafixfile", "rosettafix.file"},
args: []string{"--txpool.blacklist", "blacklist.file", "--txpool.rosettafixfile", "rosettafix.file", "--txpool.allowedtxs", "allowedtxs.txt"},
expConfig: harmonyconfig.TxPoolConfig{
BlacklistFile: "blacklist.file",
AllowedTxsFile: "allowedtxs.txt",
RosettaFixFile: "rosettafix.file",
AccountSlots: 16, // default
LocalAccountsFile: defaultConfig.TxPool.LocalAccountsFile,
Expand All @@ -894,6 +897,7 @@ func TestTxPoolFlags(t *testing.T) {
expConfig: harmonyconfig.TxPoolConfig{
BlacklistFile: "blacklist.file",
RosettaFixFile: "rosettafix.file",
AllowedTxsFile: defaultConfig.TxPool.AllowedTxsFile,
AccountSlots: 16, // default
LocalAccountsFile: defaultConfig.TxPool.LocalAccountsFile,
},
Expand All @@ -903,6 +907,7 @@ func TestTxPoolFlags(t *testing.T) {
expConfig: harmonyconfig.TxPoolConfig{
AccountSlots: 5,
BlacklistFile: "blacklist.file",
AllowedTxsFile: defaultConfig.TxPool.AllowedTxsFile,
RosettaFixFile: "rosettafix.file",
LocalAccountsFile: defaultConfig.TxPool.LocalAccountsFile,
},
Expand All @@ -911,6 +916,7 @@ func TestTxPoolFlags(t *testing.T) {
args: []string{"--txpool.locals", "locals.txt"},
expConfig: harmonyconfig.TxPoolConfig{
BlacklistFile: defaultConfig.TxPool.BlacklistFile,
AllowedTxsFile: defaultConfig.TxPool.AllowedTxsFile,
RosettaFixFile: defaultConfig.TxPool.RosettaFixFile,
AccountSlots: defaultConfig.TxPool.AccountSlots,
LocalAccountsFile: "locals.txt",
Expand Down
49 changes: 47 additions & 2 deletions cmd/harmony/main.go
Expand Up @@ -22,6 +22,7 @@ import (
rpc_common "github.com/harmony-one/harmony/rpc/common"

ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -667,6 +668,10 @@ func setupConsensusAndNode(hc harmonyconfig.HarmonyConfig, nodeConfig *nodeconfi
if err != nil {
utils.Logger().Warn().Msgf("Blacklist setup error: %s", err.Error())
}
allowedTxs, err := setupAllowedTxs(hc)
if err != nil {
utils.Logger().Warn().Msgf("AllowedTxs setup error: %s", err.Error())
}

localAccounts, err := setupLocalAccounts(hc, blacklist)
if err != nil {
Expand All @@ -687,7 +692,7 @@ func setupConsensusAndNode(hc harmonyconfig.HarmonyConfig, nodeConfig *nodeconfi
chainDBFactory = &shardchain.LDBFactory{RootDir: nodeConfig.DBDir}
}

currentNode := node.New(myHost, currentConsensus, chainDBFactory, blacklist, localAccounts, nodeConfig.ArchiveModes(), &hc)
currentNode := node.New(myHost, currentConsensus, chainDBFactory, blacklist, allowedTxs, localAccounts, nodeConfig.ArchiveModes(), &hc)

if hc.Legacy != nil && hc.Legacy.TPBroadcastInvalidTxn != nil {
currentNode.BroadcastInvalidTx = *hc.Legacy.TPBroadcastInvalidTxn
Expand Down Expand Up @@ -842,7 +847,7 @@ func setupBlacklist(hc harmonyconfig.HarmonyConfig) (map[ethCommon.Address]struc
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)
addr, err := common.ParseAddr(b32)
if err != nil {
return nil, err
}
Expand All @@ -852,6 +857,46 @@ func setupBlacklist(hc harmonyconfig.HarmonyConfig) (map[ethCommon.Address]struc
return addrMap, nil
}

func parseAllowedTxs(data []byte) (map[ethCommon.Address]core.AllowedTxData, error) {
allowedTxs := make(map[ethCommon.Address]core.AllowedTxData)
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
if len(line) != 0 { // AllowedTxs file may have trailing empty string line
substrings := strings.Split(string(line), "->")
fromStr := strings.TrimSpace(substrings[0])
txSubstrings := strings.Split(substrings[1], ":")
toStr := strings.TrimSpace(txSubstrings[0])
dataStr := strings.TrimSpace(txSubstrings[1])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is data needed? Is the combination of from and to not enough?

Assuming data is needed, do we need any error checking for the dataStr ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the data for smart contract call?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think data is necessary, this feature is only for saving tokens from compromised users. All they should do is to send the token out to a new address.

Copy link
Contributor Author

@peekpi peekpi Jul 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the data for smart contract call?

yes, the ERC20 token transfer needs to call a smart contract.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think data is necessary, this feature is only for saving tokens from compromised users. All they should do is to send the token out to a new address.

without data, users can only save ONE but ERC20.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is data needed? Is the combination of from and to not enough?

Assuming data is needed, do we need any error checking for the dataStr ?

yeah, you are right. It is better to add error checking

from, err := common.ParseAddr(fromStr)
if err != nil {
return nil, err
}
to, err := common.ParseAddr(toStr)
if err != nil {
return nil, err
}
data, err := hexutil.Decode(dataStr)
if err != nil {
return nil, err
}
allowedTxs[from] = core.AllowedTxData{
To: to,
Data: data,
}
}
}
return allowedTxs, nil
}

func setupAllowedTxs(hc harmonyconfig.HarmonyConfig) (map[ethCommon.Address]core.AllowedTxData, error) {
utils.Logger().Debug().Msgf("Using AllowedTxs file at `%s`", hc.TxPool.AllowedTxsFile)
data, err := ioutil.ReadFile(hc.TxPool.AllowedTxsFile)
if err != nil {
return nil, err
}
return parseAllowedTxs(data)
}

func setupLocalAccounts(hc harmonyconfig.HarmonyConfig, blacklist map[ethCommon.Address]struct{}) ([]ethCommon.Address, error) {
file := hc.TxPool.LocalAccountsFile
// check if file exist
Expand Down
50 changes: 50 additions & 0 deletions cmd/harmony/main_test.go
@@ -0,0 +1,50 @@
package main

import (
"bytes"
"testing"

"github.com/ethereum/go-ethereum/common"
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/core"
)

func TestAllowedTxsParse(t *testing.T) {
testData := []byte(`
0x7A6Ed0a905053A21C15cB5b4F39b561B6A3FE50f->0x855Ac656956AF761439f4a451c872E812E3900a4:0x
0x7A6Ed0a905053A21C15cB5b4F39b561B6A3FE50f->one1np293efrmv74xyjcz0kk3sn53x0fm745f2hsuc:0xa9059cbb
one1s4dvv454dtmkzsulffz3epewsyhrjq9y0g3fqz->0x985458E523dB3d53125813eD68c274899e9DfAb4:0xa9059cbb
one1s4dvv454dtmkzsulffz3epewsyhrjq9y0g3fqz->one10fhdp2g9q5azrs2ukk608x6krd4rleg0ueskug:0x
`)
expected := map[ethCommon.Address]core.AllowedTxData{
common.HexToAddress("0x7A6Ed0a905053A21C15cB5b4F39b561B6A3FE50f"): core.AllowedTxData{
To: common.HexToAddress("0x855Ac656956AF761439f4a451c872E812E3900a4"),
Data: common.FromHex("0x"),
},
common.HexToAddress("0x7A6Ed0a905053A21C15cB5b4F39b561B6A3FE50f"): core.AllowedTxData{
To: common.HexToAddress("0x985458E523dB3d53125813eD68c274899e9DfAb4"),
Data: common.FromHex("0xa9059cbb"),
},
common.HexToAddress("0x855Ac656956AF761439f4a451c872E812E3900a4"): core.AllowedTxData{
To: common.HexToAddress("0x985458E523dB3d53125813eD68c274899e9DfAb4"),
Data: common.FromHex("0xa9059cbb"),
},
common.HexToAddress("0x855Ac656956AF761439f4a451c872E812E3900a4"): core.AllowedTxData{
To: common.HexToAddress("0x7A6Ed0a905053A21C15cB5b4F39b561B6A3FE50f"),
Data: common.FromHex("0x"),
},
}
got, err := parseAllowedTxs(testData)
if err != nil {
t.Fatal(err)
}
if len(got) != len(expected) {
t.Errorf("lenght of allowed transactions not equal, got: %d expected: %d", len(got), len(expected))
}
for from, txData := range got {
expectedTxData := expected[from]
if expectedTxData.To != txData.To || !bytes.Equal(expectedTxData.Data, txData.Data) {
t.Errorf("txData not equal: got: %v expected: %v", txData, expectedTxData)
}
}
}
57 changes: 42 additions & 15 deletions core/tx_pool.go
Expand Up @@ -17,6 +17,7 @@
package core

import (
"bytes"
"fmt"
"math"
"math/big"
Expand Down Expand Up @@ -95,10 +96,12 @@ var (
ErrInvalidMsgForStakingDirective = errors.New("staking message does not match directive message")

// ErrBlacklistFrom is returned if a transaction's from/source address is blacklisted
ErrBlacklistFrom = errors.New("`from` address of transaction in blacklist")
ErrBlacklistFrom = errors.New("`from` address of transaction in blacklist and not in allowlist")

// ErrBlacklistTo is returned if a transaction's to/destination address is blacklisted
ErrBlacklistTo = errors.New("`to` address of transaction in blacklist")

ErrAllowedTxs = errors.New("transaction allowed whitelist check failed.")
)

var (
Expand Down Expand Up @@ -145,6 +148,11 @@ type blockChain interface {
SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription
}

type AllowedTxData struct {
To common.Address
Data []byte
}

// TxPoolConfig are the configuration parameters of the transaction pool.
type TxPoolConfig struct {
Locals []common.Address // Addresses that should be treated by default as local
Expand All @@ -162,7 +170,8 @@ type TxPoolConfig struct {

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
Blacklist map[common.Address]struct{} // Set of accounts that cannot be a part of any transaction
AllowedTxs map[common.Address]AllowedTxData // Set of allowed transactions can break the blocklist
}

// DefaultTxPoolConfig contains the default configurations for the transaction
Expand All @@ -181,7 +190,8 @@ var DefaultTxPoolConfig = TxPoolConfig{

Lifetime: 30 * time.Minute,

Blacklist: map[common.Address]struct{}{},
Blacklist: map[common.Address]struct{}{},
AllowedTxs: map[common.Address]AllowedTxData{},
}

// sanitize checks the provided user configurations and changes anything that's
Expand Down Expand Up @@ -213,6 +223,10 @@ func (config *TxPoolConfig) sanitize() TxPoolConfig {
utils.Logger().Warn().Msg("Sanitizing nil blacklist set")
conf.Blacklist = DefaultTxPoolConfig.Blacklist
}
if conf.AllowedTxs == nil {
utils.Logger().Warn().Msg("Sanitizing nil allowedTxs set")
conf.AllowedTxs = DefaultTxPoolConfig.AllowedTxs
}
if conf.AccountSlots == 0 {
utils.Logger().Warn().
Uint64("provided", conf.AccountSlots).
Expand Down Expand Up @@ -707,20 +721,33 @@ func (pool *TxPool) validateTx(tx types.PoolTransaction, local bool) error {
}
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)

// do whitelist check first, if tx not in whitelist, do blacklist check
if allowedTx, exists := pool.config.AllowedTxs[from]; exists {
if to := tx.To(); to == nil || *to != allowedTx.To || !bytes.Equal(tx.Data(), allowedTx.Data) {
toAddr := common.Address{}
if to != nil {
toAddr = *to
}
return errors.WithMessagef(ErrAllowedTxs, "transaction sender: %x, receiver: %x, input: %x", tx.From(), toAddr, tx.Data())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Can you change the error string of ErrBlacklistFrom and print the data and to values in the message for better logging?

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)
} else {
// do blacklist check
// 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 with data: %x", b32, tx.Data())
}
return ErrBlacklistTo
}
return ErrBlacklistTo
}
}
// Drop non-local transactions under our own minimal accepted gas price
Expand Down
1 change: 1 addition & 0 deletions internal/configs/harmony/harmony.go
Expand Up @@ -98,6 +98,7 @@ type BlsConfig struct {

type TxPoolConfig struct {
BlacklistFile string
AllowedTxsFile string
RosettaFixFile string
AccountSlots uint64
LocalAccountsFile string
Expand Down
2 changes: 2 additions & 0 deletions node/node.go
Expand Up @@ -956,6 +956,7 @@ func New(
consensusObj *consensus.Consensus,
chainDBFactory shardchain.DBFactory,
blacklist map[common.Address]struct{},
allowedTxs map[common.Address]core.AllowedTxData,
localAccounts []common.Address,
isArchival map[uint32]bool,
harmonyconfig *harmonyconfig.HarmonyConfig,
Expand Down Expand Up @@ -1031,6 +1032,7 @@ func New(
}

txPoolConfig.Blacklist = blacklist
txPoolConfig.AllowedTxs = allowedTxs
txPoolConfig.Journal = fmt.Sprintf("%v/%v", node.NodeConfig.DBDir, txPoolConfig.Journal)
node.TxPool = core.NewTxPool(txPoolConfig, node.Blockchain().Config(), blockchain, node.TransactionErrorSink)
node.CxPool = core.NewCxPool(core.CxPoolSize)
Expand Down