Merge pull request #56 from vulcanize/v1.10.1-statediff-0.0.15
V1.10.1 statediff 0.0.15
This commit is contained in:
		
						commit
						daa2cdde22
					
				
							
								
								
									
										1
									
								
								.github/workflows/on-master.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/on-master.yaml
									
									
									
									
										vendored
									
									
								
							| @ -3,6 +3,7 @@ name: Docker Build and publish to Github | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - v1.10.1-statediff | ||||
|       - v1.9.25-statediff | ||||
|       - v1.9.24-statediff | ||||
|       - v1.9.23-statediff | ||||
|  | ||||
| @ -180,7 +180,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { | ||||
| 			EnableWriteLoop: ctx.GlobalBool(utils.StateDiffWritingFlag.Name), | ||||
| 			NumWorkers:      ctx.GlobalUint(utils.StateDiffWorkersFlag.Name), | ||||
| 		} | ||||
| 		utils.RegisterStateDiffService(stack, backend, params) | ||||
| 		utils.RegisterStateDiffService(stack, backend, &cfg.Eth, params) | ||||
| 	} | ||||
| 
 | ||||
| 	// Configure GraphQL if requested
 | ||||
|  | ||||
| @ -46,7 +46,6 @@ import ( | ||||
| 	"github.com/ethereum/go-ethereum/eth/downloader" | ||||
| 	"github.com/ethereum/go-ethereum/eth/ethconfig" | ||||
| 	"github.com/ethereum/go-ethereum/eth/gasprice" | ||||
| 	"github.com/ethereum/go-ethereum/eth/tracers" | ||||
| 	"github.com/ethereum/go-ethereum/ethdb" | ||||
| 	"github.com/ethereum/go-ethereum/ethstats" | ||||
| 	"github.com/ethereum/go-ethereum/graphql" | ||||
| @ -1735,8 +1734,8 @@ func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, cfg node.C | ||||
| } | ||||
| 
 | ||||
| // RegisterStateDiffService configures and registers a service to stream state diff data over RPC
 | ||||
| func RegisterStateDiffService(stack *node.Node, ethServ *eth.Ethereum, params statediff.ServiceParams) { | ||||
| 	if err := statediff.New(stack, ethServ, params); err != nil { | ||||
| func RegisterStateDiffService(stack *node.Node, ethServ *eth.Ethereum, cfg *ethconfig.Config, params statediff.ServiceParams) { | ||||
| 	if err := statediff.New(stack, ethServ, cfg, params); err != nil { | ||||
| 		Fatalf("Failed to register the Statediff service: %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -36,6 +36,7 @@ import ( | ||||
| 	"github.com/ethereum/go-ethereum/crypto" | ||||
| 	"github.com/ethereum/go-ethereum/eth" | ||||
| 	"github.com/ethereum/go-ethereum/eth/downloader" | ||||
| 	"github.com/ethereum/go-ethereum/eth/ethconfig" | ||||
| 	"github.com/ethereum/go-ethereum/log" | ||||
| 	"github.com/ethereum/go-ethereum/miner" | ||||
| 	"github.com/ethereum/go-ethereum/node" | ||||
|  | ||||
| @ -37,6 +37,7 @@ import ( | ||||
| 	"github.com/ethereum/go-ethereum/crypto" | ||||
| 	"github.com/ethereum/go-ethereum/eth" | ||||
| 	"github.com/ethereum/go-ethereum/eth/downloader" | ||||
| 	"github.com/ethereum/go-ethereum/eth/ethconfig" | ||||
| 	"github.com/ethereum/go-ethereum/log" | ||||
| 	"github.com/ethereum/go-ethereum/miner" | ||||
| 	"github.com/ethereum/go-ethereum/node" | ||||
|  | ||||
| @ -21,10 +21,10 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	VersionMajor = 1        // Major version component of the current release
 | ||||
| 	VersionMinor = 10       // Minor version component of the current release
 | ||||
| 	VersionPatch = 1        // Patch version component of the current release
 | ||||
| 	VersionMeta  = "stable" // Version metadata to append to the version string
 | ||||
| 	VersionMajor = 1                  // Major version component of the current release
 | ||||
| 	VersionMinor = 10                 // Minor version component of the current release
 | ||||
| 	VersionPatch = 1                  // Patch version component of the current release
 | ||||
| 	VersionMeta  = "statediff-0.0.15" // Version metadata to append to the version string
 | ||||
| ) | ||||
| 
 | ||||
| // Version holds the textual version string.
 | ||||
|  | ||||
| @ -17,11 +17,15 @@ | ||||
| package indexer_test | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| 
 | ||||
| 	"github.com/ethereum/go-ethereum/common" | ||||
| 	"github.com/ethereum/go-ethereum/params" | ||||
| 	"github.com/ethereum/go-ethereum/statediff/indexer" | ||||
| 	"github.com/ethereum/go-ethereum/statediff/indexer/ipfs/ipld" | ||||
| 	"github.com/ethereum/go-ethereum/statediff/indexer/mocks" | ||||
| 	"github.com/ethereum/go-ethereum/statediff/indexer/models" | ||||
| 	"github.com/ethereum/go-ethereum/statediff/indexer/postgres" | ||||
| @ -30,6 +34,7 @@ import ( | ||||
| 	"github.com/ipfs/go-cid" | ||||
| 	blockstore "github.com/ipfs/go-ipfs-blockstore" | ||||
| 	dshelp "github.com/ipfs/go-ipfs-ds-help" | ||||
| 	"github.com/multiformats/go-multihash" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| @ -38,6 +43,11 @@ var ( | ||||
| 	ind       *indexer.StateDiffIndexer | ||||
| 	ipfsPgGet = `SELECT data FROM public.blocks | ||||
| 					WHERE key = $1` | ||||
| 	tx1, tx2, tx3, rct1, rct2, rct3      []byte | ||||
| 	mockBlock                            *types.Block | ||||
| 	headerCID, trx1CID, trx2CID, trx3CID cid.Cid | ||||
| 	rct1CID, rct2CID, rct3CID            cid.Cid | ||||
| 	state1CID, state2CID, storageCID     cid.Cid | ||||
| ) | ||||
| 
 | ||||
| func expectTrue(t *testing.T, value bool) { | ||||
| @ -46,6 +56,53 @@ func expectTrue(t *testing.T, value bool) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
| 	mockBlock = mocks.MockBlock | ||||
| 	txs, rcts := mocks.MockBlock.Transactions(), mocks.MockReceipts | ||||
| 
 | ||||
| 	buf := new(bytes.Buffer) | ||||
| 	txs.EncodeIndex(0, buf) | ||||
| 	tx1 = make([]byte, buf.Len()) | ||||
| 	copy(tx1, buf.Bytes()) | ||||
| 	buf.Reset() | ||||
| 
 | ||||
| 	txs.EncodeIndex(1, buf) | ||||
| 	tx2 = make([]byte, buf.Len()) | ||||
| 	copy(tx2, buf.Bytes()) | ||||
| 	buf.Reset() | ||||
| 
 | ||||
| 	txs.EncodeIndex(2, buf) | ||||
| 	tx3 = make([]byte, buf.Len()) | ||||
| 	copy(tx3, buf.Bytes()) | ||||
| 	buf.Reset() | ||||
| 
 | ||||
| 	rcts.EncodeIndex(0, buf) | ||||
| 	rct1 = make([]byte, buf.Len()) | ||||
| 	copy(rct1, buf.Bytes()) | ||||
| 	buf.Reset() | ||||
| 
 | ||||
| 	rcts.EncodeIndex(1, buf) | ||||
| 	rct2 = make([]byte, buf.Len()) | ||||
| 	copy(rct2, buf.Bytes()) | ||||
| 	buf.Reset() | ||||
| 
 | ||||
| 	rcts.EncodeIndex(2, buf) | ||||
| 	rct3 = make([]byte, buf.Len()) | ||||
| 	copy(rct3, buf.Bytes()) | ||||
| 	buf.Reset() | ||||
| 
 | ||||
| 	headerCID, _ = ipld.RawdataToCid(ipld.MEthHeader, mocks.MockHeaderRlp, multihash.KECCAK_256) | ||||
| 	trx1CID, _ = ipld.RawdataToCid(ipld.MEthTx, tx1, multihash.KECCAK_256) | ||||
| 	trx2CID, _ = ipld.RawdataToCid(ipld.MEthTx, tx2, multihash.KECCAK_256) | ||||
| 	trx3CID, _ = ipld.RawdataToCid(ipld.MEthTx, tx3, multihash.KECCAK_256) | ||||
| 	rct1CID, _ = ipld.RawdataToCid(ipld.MEthTxReceipt, rct1, multihash.KECCAK_256) | ||||
| 	rct2CID, _ = ipld.RawdataToCid(ipld.MEthTxReceipt, rct2, multihash.KECCAK_256) | ||||
| 	rct3CID, _ = ipld.RawdataToCid(ipld.MEthTxReceipt, rct3, multihash.KECCAK_256) | ||||
| 	state1CID, _ = ipld.RawdataToCid(ipld.MEthStateTrie, mocks.ContractLeafNode, multihash.KECCAK_256) | ||||
| 	state2CID, _ = ipld.RawdataToCid(ipld.MEthStateTrie, mocks.AccountLeafNode, multihash.KECCAK_256) | ||||
| 	storageCID, _ = ipld.RawdataToCid(ipld.MEthStorageTrie, mocks.StorageLeafNode, multihash.KECCAK_256) | ||||
| } | ||||
| 
 | ||||
| func setup(t *testing.T) { | ||||
| 	db, err = shared.SetupDB() | ||||
| 	if err != nil { | ||||
| @ -54,7 +111,7 @@ func setup(t *testing.T) { | ||||
| 	ind = indexer.NewStateDiffIndexer(params.MainnetChainConfig, db) | ||||
| 	var tx *indexer.BlockTx | ||||
| 	tx, err = ind.PushBlock( | ||||
| 		mocks.MockBlock, | ||||
| 		mockBlock, | ||||
| 		mocks.MockReceipts, | ||||
| 		mocks.MockBlock.Difficulty()) | ||||
| 	if err != nil { | ||||
| @ -94,7 +151,7 @@ func TestPublishAndIndexer(t *testing.T) { | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 		shared.ExpectEqual(t, header.CID, mocks.HeaderCID.String()) | ||||
| 		shared.ExpectEqual(t, header.CID, headerCID.String()) | ||||
| 		shared.ExpectEqual(t, header.TD, mocks.MockBlock.Difficulty().String()) | ||||
| 		shared.ExpectEqual(t, header.Reward, "5000000000000011250") | ||||
| 		dc, err := cid.Decode(header.CID) | ||||
| @ -123,9 +180,9 @@ func TestPublishAndIndexer(t *testing.T) { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 		shared.ExpectEqual(t, len(trxs), 3) | ||||
| 		expectTrue(t, shared.ListContainsString(trxs, mocks.Trx1CID.String())) | ||||
| 		expectTrue(t, shared.ListContainsString(trxs, mocks.Trx2CID.String())) | ||||
| 		expectTrue(t, shared.ListContainsString(trxs, mocks.Trx3CID.String())) | ||||
| 		expectTrue(t, shared.ListContainsString(trxs, trx1CID.String())) | ||||
| 		expectTrue(t, shared.ListContainsString(trxs, trx2CID.String())) | ||||
| 		expectTrue(t, shared.ListContainsString(trxs, trx3CID.String())) | ||||
| 		// and published
 | ||||
| 		for _, c := range trxs { | ||||
| 			dc, err := cid.Decode(c) | ||||
| @ -140,12 +197,12 @@ func TestPublishAndIndexer(t *testing.T) { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
| 			switch c { | ||||
| 			case mocks.Trx1CID.String(): | ||||
| 				shared.ExpectEqual(t, data, mocks.MockTransactions.GetRlp(0)) | ||||
| 			case mocks.Trx2CID.String(): | ||||
| 				shared.ExpectEqual(t, data, mocks.MockTransactions.GetRlp(1)) | ||||
| 			case mocks.Trx3CID.String(): | ||||
| 				shared.ExpectEqual(t, data, mocks.MockTransactions.GetRlp(2)) | ||||
| 			case trx1CID.String(): | ||||
| 				shared.ExpectEqual(t, data, tx1) | ||||
| 			case trx2CID.String(): | ||||
| 				shared.ExpectEqual(t, data, tx2) | ||||
| 			case trx3CID.String(): | ||||
| 				shared.ExpectEqual(t, data, tx3) | ||||
| 			} | ||||
| 		} | ||||
| 	}) | ||||
| @ -164,9 +221,9 @@ func TestPublishAndIndexer(t *testing.T) { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 		shared.ExpectEqual(t, len(rcts), 3) | ||||
| 		expectTrue(t, shared.ListContainsString(rcts, mocks.Rct1CID.String())) | ||||
| 		expectTrue(t, shared.ListContainsString(rcts, mocks.Rct2CID.String())) | ||||
| 		expectTrue(t, shared.ListContainsString(rcts, mocks.Rct3CID.String())) | ||||
| 		expectTrue(t, shared.ListContainsString(rcts, rct1CID.String())) | ||||
| 		expectTrue(t, shared.ListContainsString(rcts, rct2CID.String())) | ||||
| 		expectTrue(t, shared.ListContainsString(rcts, rct3CID.String())) | ||||
| 		// and published
 | ||||
| 		for _, c := range rcts { | ||||
| 			dc, err := cid.Decode(c) | ||||
| @ -181,8 +238,8 @@ func TestPublishAndIndexer(t *testing.T) { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
| 			switch c { | ||||
| 			case mocks.Rct1CID.String(): | ||||
| 				shared.ExpectEqual(t, data, mocks.MockReceipts.GetRlp(0)) | ||||
| 			case rct1CID.String(): | ||||
| 				shared.ExpectEqual(t, data, rct1) | ||||
| 				var postStatus uint64 | ||||
| 				pgStr = `SELECT post_status FROM eth.receipt_cids WHERE cid = $1` | ||||
| 				err = db.Get(&postStatus, pgStr, c) | ||||
| @ -190,8 +247,8 @@ func TestPublishAndIndexer(t *testing.T) { | ||||
| 					t.Fatal(err) | ||||
| 				} | ||||
| 				shared.ExpectEqual(t, postStatus, mocks.ExpectedPostStatus) | ||||
| 			case mocks.Rct2CID.String(): | ||||
| 				shared.ExpectEqual(t, data, mocks.MockReceipts.GetRlp(1)) | ||||
| 			case rct2CID.String(): | ||||
| 				shared.ExpectEqual(t, data, rct2) | ||||
| 				var postState string | ||||
| 				pgStr = `SELECT post_state FROM eth.receipt_cids WHERE cid = $1` | ||||
| 				err = db.Get(&postState, pgStr, c) | ||||
| @ -199,8 +256,8 @@ func TestPublishAndIndexer(t *testing.T) { | ||||
| 					t.Fatal(err) | ||||
| 				} | ||||
| 				shared.ExpectEqual(t, postState, mocks.ExpectedPostState1) | ||||
| 			case mocks.Rct3CID.String(): | ||||
| 				shared.ExpectEqual(t, data, mocks.MockReceipts.GetRlp(2)) | ||||
| 			case rct3CID.String(): | ||||
| 				shared.ExpectEqual(t, data, rct3) | ||||
| 				var postState string | ||||
| 				pgStr = `SELECT post_state FROM eth.receipt_cids WHERE cid = $1` | ||||
| 				err = db.Get(&postState, pgStr, c) | ||||
| @ -243,7 +300,7 @@ func TestPublishAndIndexer(t *testing.T) { | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
| 			if stateNode.CID == mocks.State1CID.String() { | ||||
| 			if stateNode.CID == state1CID.String() { | ||||
| 				shared.ExpectEqual(t, stateNode.NodeType, 2) | ||||
| 				shared.ExpectEqual(t, stateNode.StateKey, common.BytesToHash(mocks.ContractLeafKey).Hex()) | ||||
| 				shared.ExpectEqual(t, stateNode.Path, []byte{'\x06'}) | ||||
| @ -257,7 +314,7 @@ func TestPublishAndIndexer(t *testing.T) { | ||||
| 					Nonce:       1, | ||||
| 				}) | ||||
| 			} | ||||
| 			if stateNode.CID == mocks.State2CID.String() { | ||||
| 			if stateNode.CID == state2CID.String() { | ||||
| 				shared.ExpectEqual(t, stateNode.NodeType, 2) | ||||
| 				shared.ExpectEqual(t, stateNode.StateKey, common.BytesToHash(mocks.AccountLeafKey).Hex()) | ||||
| 				shared.ExpectEqual(t, stateNode.Path, []byte{'\x0c'}) | ||||
| @ -290,7 +347,7 @@ func TestPublishAndIndexer(t *testing.T) { | ||||
| 		} | ||||
| 		shared.ExpectEqual(t, len(storageNodes), 1) | ||||
| 		shared.ExpectEqual(t, storageNodes[0], models.StorageNodeWithStateKeyModel{ | ||||
| 			CID:        mocks.StorageCID.String(), | ||||
| 			CID:        storageCID.String(), | ||||
| 			NodeType:   2, | ||||
| 			StorageKey: common.BytesToHash(mocks.StorageLeafKey).Hex(), | ||||
| 			StateKey:   common.BytesToHash(mocks.ContractLeafKey).Hex(), | ||||
|  | ||||
| @ -31,9 +31,6 @@ import ( | ||||
| 	"github.com/ethereum/go-ethereum/log" | ||||
| 	"github.com/ethereum/go-ethereum/params" | ||||
| 	"github.com/ethereum/go-ethereum/rlp" | ||||
| 	"github.com/multiformats/go-multihash" | ||||
| 
 | ||||
| 	"github.com/ethereum/go-ethereum/statediff/indexer/ipfs/ipld" | ||||
| 	"github.com/ethereum/go-ethereum/statediff/testhelpers" | ||||
| 	sdtypes "github.com/ethereum/go-ethereum/statediff/types" | ||||
| ) | ||||
| @ -52,13 +49,11 @@ var ( | ||||
| 		Extra:       []byte{}, | ||||
| 	} | ||||
| 	MockTransactions, MockReceipts, SenderAddr        = createTransactionsAndReceipts() | ||||
| 	ReceiptsRlp, _                                    = rlp.EncodeToBytes(MockReceipts) | ||||
| 	MockBlock                                         = types.NewBlock(&MockHeader, MockTransactions, nil, MockReceipts, new(trie.Trie)) | ||||
| 	MockHeaderRlp, _                                  = rlp.EncodeToBytes(MockBlock.Header()) | ||||
| 	Address                                           = common.HexToAddress("0xaE9BEa628c4Ce503DcFD7E305CaB4e29E7476592") | ||||
| 	AnotherAddress                                    = common.HexToAddress("0xaE9BEa628c4Ce503DcFD7E305CaB4e29E7476593") | ||||
| 	ContractAddress                                   = crypto.CreateAddress(SenderAddr, MockTransactions[2].Nonce()) | ||||
| 	ContractHash                                      = crypto.Keccak256Hash(ContractAddress.Bytes()).String() | ||||
| 	MockContractByteCode                              = []byte{0, 1, 2, 3, 4, 5} | ||||
| 	mockTopic11                                       = common.HexToHash("0x04") | ||||
| 	mockTopic12                                       = common.HexToHash("0x06") | ||||
| @ -77,16 +72,6 @@ var ( | ||||
| 		Topics:  []common.Hash{mockTopic21, mockTopic22}, | ||||
| 		Data:    []byte{}, | ||||
| 	} | ||||
| 	HeaderCID, _  = ipld.RawdataToCid(ipld.MEthHeader, MockHeaderRlp, multihash.KECCAK_256) | ||||
| 	Trx1CID, _    = ipld.RawdataToCid(ipld.MEthTx, MockTransactions.GetRlp(0), multihash.KECCAK_256) | ||||
| 	Trx2CID, _    = ipld.RawdataToCid(ipld.MEthTx, MockTransactions.GetRlp(1), multihash.KECCAK_256) | ||||
| 	Trx3CID, _    = ipld.RawdataToCid(ipld.MEthTx, MockTransactions.GetRlp(2), multihash.KECCAK_256) | ||||
| 	Rct1CID, _    = ipld.RawdataToCid(ipld.MEthTxReceipt, MockReceipts.GetRlp(0), multihash.KECCAK_256) | ||||
| 	Rct2CID, _    = ipld.RawdataToCid(ipld.MEthTxReceipt, MockReceipts.GetRlp(1), multihash.KECCAK_256) | ||||
| 	Rct3CID, _    = ipld.RawdataToCid(ipld.MEthTxReceipt, MockReceipts.GetRlp(2), multihash.KECCAK_256) | ||||
| 	State1CID, _  = ipld.RawdataToCid(ipld.MEthStateTrie, ContractLeafNode, multihash.KECCAK_256) | ||||
| 	State2CID, _  = ipld.RawdataToCid(ipld.MEthStateTrie, AccountLeafNode, multihash.KECCAK_256) | ||||
| 	StorageCID, _ = ipld.RawdataToCid(ipld.MEthStorageTrie, StorageLeafNode, multihash.KECCAK_256) | ||||
| 
 | ||||
| 	// statediff data
 | ||||
| 	storageLocation    = common.HexToHash("0") | ||||
| @ -193,5 +178,6 @@ func createTransactionsAndReceipts() (types.Transactions, types.Receipts, common | ||||
| 	mockReceipt3 := types.NewReceipt(common.HexToHash("0x2").Bytes(), false, 75) | ||||
| 	mockReceipt3.Logs = []*types.Log{} | ||||
| 	mockReceipt3.TxHash = signedTrx3.Hash() | ||||
| 
 | ||||
| 	return types.Transactions{signedTrx1, signedTrx2, signedTrx3}, types.Receipts{mockReceipt1, mockReceipt2, mockReceipt3}, SenderAddr | ||||
| } | ||||
|  | ||||
| @ -23,6 +23,8 @@ import ( | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 
 | ||||
| 	"github.com/ethereum/go-ethereum/eth/ethconfig" | ||||
| 
 | ||||
| 	"github.com/ethereum/go-ethereum/common" | ||||
| 	"github.com/ethereum/go-ethereum/core" | ||||
| 	"github.com/ethereum/go-ethereum/core/state" | ||||
| @ -146,13 +148,13 @@ func NewBlockCache(max uint) blockCache { | ||||
| 
 | ||||
| // New creates a new statediff.Service
 | ||||
| // func New(stack *node.Node, ethServ *eth.Ethereum, dbParams *DBParams, enableWriteLoop bool) error {
 | ||||
| func New(stack *node.Node, ethServ *eth.Ethereum, params ServiceParams) error { | ||||
| func New(stack *node.Node, ethServ *eth.Ethereum, cfg *ethconfig.Config, params ServiceParams) error { | ||||
| 	blockChain := ethServ.BlockChain() | ||||
| 	var indexer ind.Indexer | ||||
| 	if params.DBParams != nil { | ||||
| 		info := nodeinfo.Info{ | ||||
| 			GenesisBlock: blockChain.Genesis().Hash().Hex(), | ||||
| 			NetworkID:    strconv.FormatUint(ethServ.NetVersion(), 10), | ||||
| 			NetworkID:    strconv.FormatUint(cfg.NetworkId, 10), | ||||
| 			ChainID:      blockChain.Config().ChainID.Uint64(), | ||||
| 			ID:           params.DBParams.ID, | ||||
| 			ClientName:   params.DBParams.ClientName, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user