Feature/mailserver registry smart contract (#1135)

* add verifier and test using simulated backend

* add ContractCaller

* commit simulated backend after deploy and after smart contract writes

* use bind.NewKeyedTransactor for all transactions in tests

* rename RegistryVerifier to Verifier

* initialize contract verifier if MailServerRegistryAddress config is set

* use contractAddress.Hash()

* refactoring

* use fmt.Sprintf to format contract address in logs

* fix test and lint warnings

* update Gopkg.lock

* update Gopkg.lock once more
This commit is contained in:
Andrea Franz 2018-08-20 15:55:43 +02:00 committed by GitHub
parent cf21f981f7
commit 874a3e8151
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 778 additions and 18 deletions

2
Gopkg.lock generated
View File

@ -1025,6 +1025,8 @@
"github.com/btcsuite/btcutil/base58", "github.com/btcsuite/btcutil/base58",
"github.com/ethereum/go-ethereum", "github.com/ethereum/go-ethereum",
"github.com/ethereum/go-ethereum/accounts", "github.com/ethereum/go-ethereum/accounts",
"github.com/ethereum/go-ethereum/accounts/abi",
"github.com/ethereum/go-ethereum/accounts/abi/bind",
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends", "github.com/ethereum/go-ethereum/accounts/abi/bind/backends",
"github.com/ethereum/go-ethereum/accounts/keystore", "github.com/ethereum/go-ethereum/accounts/keystore",
"github.com/ethereum/go-ethereum/common", "github.com/ethereum/go-ethereum/common",

View File

@ -0,0 +1,67 @@
package contracts
import (
"context"
"math/big"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
type RPCClient interface {
CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error
}
type ContractCaller struct {
c RPCClient
}
func NewContractCaller(c RPCClient) *ContractCaller {
return &ContractCaller{
c: c,
}
}
// CodeAt returns the contract code of the given account.
// The block number can be nil, in which case the code is taken from the latest known block.
func (cc *ContractCaller) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) {
var result hexutil.Bytes
err := cc.c.CallContext(ctx, &result, "eth_getCode", account, "latest")
return result, err
}
// CallContract executes a message call transaction, which is directly executed in the VM
// of the node, but never mined into the blockchain.
//
// blockNumber selects the block height at which the call runs. It can be nil, in which
// case the code is taken from the latest known block. Note that state from very old
// blocks might not be available.
func (cc *ContractCaller) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
var hex hexutil.Bytes
err := cc.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), "latest")
if err != nil {
return nil, err
}
return hex, nil
}
func toCallArg(msg ethereum.CallMsg) interface{} {
arg := map[string]interface{}{
"from": msg.From,
"to": msg.To,
}
if len(msg.Data) > 0 {
arg["data"] = hexutil.Bytes(msg.Data)
}
if msg.Value != nil {
arg["value"] = (*hexutil.Big)(msg.Value)
}
if msg.Gas != 0 {
arg["gas"] = hexutil.Uint64(msg.Gas)
}
if msg.GasPrice != nil {
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
}
return arg
}

View File

@ -0,0 +1,535 @@
// Code generated - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package registry
import (
"strings"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
)
// RegistryABI is the input ABI used to generate the binding from.
const RegistryABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"_newController\",\"type\":\"address\"}],\"name\":\"changeController\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"a\",\"type\":\"bytes\"}],\"name\":\"remove\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"a\",\"type\":\"bytes\"}],\"name\":\"exists\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"a\",\"type\":\"bytes\"}],\"name\":\"add\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"controller\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"a\",\"type\":\"bytes\"}],\"name\":\"MailServerAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"a\",\"type\":\"bytes\"}],\"name\":\"MailServerRemoved\",\"type\":\"event\"}]"
// RegistryBin is the compiled bytecode used for deploying new contracts.
const RegistryBin = `6080604052336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610511806100536000396000f30060806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633cebb8231461007257806358edef4c146100b557806379fc09a2146100f0578063ba65811114610143578063f77c47911461017e575b600080fd5b34801561007e57600080fd5b506100b3600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506101d5565b005b3480156100c157600080fd5b506100ee600480360381019080803590602001908201803590602001919091929391929390505050610273565b005b3480156100fc57600080fd5b50610129600480360381019080803590602001908201803590602001919091929391929390505050610396565b604051808215151515815260200191505060405180910390f35b34801561014f57600080fd5b5061017c6004803603810190808035906020019082018035906020019190919293919293905050506103d3565b005b34801561018a57600080fd5b506101936104c0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561023057600080fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102ce57600080fd5b6001828260405180838380828437820191505092505050908152602001604051809103902060009054906101000a900460ff16151561030c57600080fd5b6001828260405180838380828437820191505092505050908152602001604051809103902060006101000a81549060ff02191690557f44e7d85a87eeb950b8bdc144d44b0b474be610d1953607251a0130edc10a222b8282604051808060200182810382528484828181526020019250808284378201915050935050505060405180910390a15050565b60006001838360405180838380828437820191505092505050908152602001604051809103902060009054906101000a900460ff16905092915050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561042e57600080fd5b600180838360405180838380828437820191505092505050908152602001604051809103902060006101000a81548160ff0219169083151502179055507fcb379cb5890ec9889055734e1561cdc353a342d46d8d650c8c3a8d66383c29cd8282604051808060200182810382528484828181526020019250808284378201915050935050505060405180910390a15050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a723058205234376a4ed154ce31a74225ac2258d6a4a82fd5fef967aaaf17c005c40b6f370029`
// DeployRegistry deploys a new Ethereum contract, binding an instance of Registry to it.
func DeployRegistry(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Registry, error) {
parsed, err := abi.JSON(strings.NewReader(RegistryABI))
if err != nil {
return common.Address{}, nil, nil, err
}
address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(RegistryBin), backend)
if err != nil {
return common.Address{}, nil, nil, err
}
return address, tx, &Registry{RegistryCaller: RegistryCaller{contract: contract}, RegistryTransactor: RegistryTransactor{contract: contract}, RegistryFilterer: RegistryFilterer{contract: contract}}, nil
}
// Registry is an auto generated Go binding around an Ethereum contract.
type Registry struct {
RegistryCaller // Read-only binding to the contract
RegistryTransactor // Write-only binding to the contract
RegistryFilterer // Log filterer for contract events
}
// RegistryCaller is an auto generated read-only Go binding around an Ethereum contract.
type RegistryCaller struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// RegistryTransactor is an auto generated write-only Go binding around an Ethereum contract.
type RegistryTransactor struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// RegistryFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
type RegistryFilterer struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// RegistrySession is an auto generated Go binding around an Ethereum contract,
// with pre-set call and transact options.
type RegistrySession struct {
Contract *Registry // Generic contract binding to set the session for
CallOpts bind.CallOpts // Call options to use throughout this session
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
}
// RegistryCallerSession is an auto generated read-only Go binding around an Ethereum contract,
// with pre-set call options.
type RegistryCallerSession struct {
Contract *RegistryCaller // Generic contract caller binding to set the session for
CallOpts bind.CallOpts // Call options to use throughout this session
}
// RegistryTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
// with pre-set transact options.
type RegistryTransactorSession struct {
Contract *RegistryTransactor // Generic contract transactor binding to set the session for
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
}
// RegistryRaw is an auto generated low-level Go binding around an Ethereum contract.
type RegistryRaw struct {
Contract *Registry // Generic contract binding to access the raw methods on
}
// RegistryCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
type RegistryCallerRaw struct {
Contract *RegistryCaller // Generic read-only contract binding to access the raw methods on
}
// RegistryTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
type RegistryTransactorRaw struct {
Contract *RegistryTransactor // Generic write-only contract binding to access the raw methods on
}
// NewRegistry creates a new instance of Registry, bound to a specific deployed contract.
func NewRegistry(address common.Address, backend bind.ContractBackend) (*Registry, error) {
contract, err := bindRegistry(address, backend, backend, backend)
if err != nil {
return nil, err
}
return &Registry{RegistryCaller: RegistryCaller{contract: contract}, RegistryTransactor: RegistryTransactor{contract: contract}, RegistryFilterer: RegistryFilterer{contract: contract}}, nil
}
// NewRegistryCaller creates a new read-only instance of Registry, bound to a specific deployed contract.
func NewRegistryCaller(address common.Address, caller bind.ContractCaller) (*RegistryCaller, error) {
contract, err := bindRegistry(address, caller, nil, nil)
if err != nil {
return nil, err
}
return &RegistryCaller{contract: contract}, nil
}
// NewRegistryTransactor creates a new write-only instance of Registry, bound to a specific deployed contract.
func NewRegistryTransactor(address common.Address, transactor bind.ContractTransactor) (*RegistryTransactor, error) {
contract, err := bindRegistry(address, nil, transactor, nil)
if err != nil {
return nil, err
}
return &RegistryTransactor{contract: contract}, nil
}
// NewRegistryFilterer creates a new log filterer instance of Registry, bound to a specific deployed contract.
func NewRegistryFilterer(address common.Address, filterer bind.ContractFilterer) (*RegistryFilterer, error) {
contract, err := bindRegistry(address, nil, nil, filterer)
if err != nil {
return nil, err
}
return &RegistryFilterer{contract: contract}, nil
}
// bindRegistry binds a generic wrapper to an already deployed contract.
func bindRegistry(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
parsed, err := abi.JSON(strings.NewReader(RegistryABI))
if err != nil {
return nil, err
}
return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
}
// Call invokes the (constant) contract method with params as input values and
// sets the output to result. The result type might be a single field for simple
// returns, a slice of interfaces for anonymous returns and a struct for named
// returns.
func (_Registry *RegistryRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
return _Registry.Contract.RegistryCaller.contract.Call(opts, result, method, params...)
}
// Transfer initiates a plain transaction to move funds to the contract, calling
// its default method if one is available.
func (_Registry *RegistryRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
return _Registry.Contract.RegistryTransactor.contract.Transfer(opts)
}
// Transact invokes the (paid) contract method with params as input values.
func (_Registry *RegistryRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
return _Registry.Contract.RegistryTransactor.contract.Transact(opts, method, params...)
}
// Call invokes the (constant) contract method with params as input values and
// sets the output to result. The result type might be a single field for simple
// returns, a slice of interfaces for anonymous returns and a struct for named
// returns.
func (_Registry *RegistryCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
return _Registry.Contract.contract.Call(opts, result, method, params...)
}
// Transfer initiates a plain transaction to move funds to the contract, calling
// its default method if one is available.
func (_Registry *RegistryTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
return _Registry.Contract.contract.Transfer(opts)
}
// Transact invokes the (paid) contract method with params as input values.
func (_Registry *RegistryTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
return _Registry.Contract.contract.Transact(opts, method, params...)
}
// Controller is a free data retrieval call binding the contract method 0xf77c4791.
//
// Solidity: function controller() constant returns(address)
func (_Registry *RegistryCaller) Controller(opts *bind.CallOpts) (common.Address, error) {
var (
ret0 = new(common.Address)
)
out := ret0
err := _Registry.contract.Call(opts, out, "controller")
return *ret0, err
}
// Controller is a free data retrieval call binding the contract method 0xf77c4791.
//
// Solidity: function controller() constant returns(address)
func (_Registry *RegistrySession) Controller() (common.Address, error) {
return _Registry.Contract.Controller(&_Registry.CallOpts)
}
// Controller is a free data retrieval call binding the contract method 0xf77c4791.
//
// Solidity: function controller() constant returns(address)
func (_Registry *RegistryCallerSession) Controller() (common.Address, error) {
return _Registry.Contract.Controller(&_Registry.CallOpts)
}
// Exists is a free data retrieval call binding the contract method 0x79fc09a2.
//
// Solidity: function exists(a bytes) constant returns(bool)
func (_Registry *RegistryCaller) Exists(opts *bind.CallOpts, a []byte) (bool, error) {
var (
ret0 = new(bool)
)
out := ret0
err := _Registry.contract.Call(opts, out, "exists", a)
return *ret0, err
}
// Exists is a free data retrieval call binding the contract method 0x79fc09a2.
//
// Solidity: function exists(a bytes) constant returns(bool)
func (_Registry *RegistrySession) Exists(a []byte) (bool, error) {
return _Registry.Contract.Exists(&_Registry.CallOpts, a)
}
// Exists is a free data retrieval call binding the contract method 0x79fc09a2.
//
// Solidity: function exists(a bytes) constant returns(bool)
func (_Registry *RegistryCallerSession) Exists(a []byte) (bool, error) {
return _Registry.Contract.Exists(&_Registry.CallOpts, a)
}
// Add is a paid mutator transaction binding the contract method 0xba658111.
//
// Solidity: function add(a bytes) returns()
func (_Registry *RegistryTransactor) Add(opts *bind.TransactOpts, a []byte) (*types.Transaction, error) {
return _Registry.contract.Transact(opts, "add", a)
}
// Add is a paid mutator transaction binding the contract method 0xba658111.
//
// Solidity: function add(a bytes) returns()
func (_Registry *RegistrySession) Add(a []byte) (*types.Transaction, error) {
return _Registry.Contract.Add(&_Registry.TransactOpts, a)
}
// Add is a paid mutator transaction binding the contract method 0xba658111.
//
// Solidity: function add(a bytes) returns()
func (_Registry *RegistryTransactorSession) Add(a []byte) (*types.Transaction, error) {
return _Registry.Contract.Add(&_Registry.TransactOpts, a)
}
// ChangeController is a paid mutator transaction binding the contract method 0x3cebb823.
//
// Solidity: function changeController(_newController address) returns()
func (_Registry *RegistryTransactor) ChangeController(opts *bind.TransactOpts, _newController common.Address) (*types.Transaction, error) {
return _Registry.contract.Transact(opts, "changeController", _newController)
}
// ChangeController is a paid mutator transaction binding the contract method 0x3cebb823.
//
// Solidity: function changeController(_newController address) returns()
func (_Registry *RegistrySession) ChangeController(_newController common.Address) (*types.Transaction, error) {
return _Registry.Contract.ChangeController(&_Registry.TransactOpts, _newController)
}
// ChangeController is a paid mutator transaction binding the contract method 0x3cebb823.
//
// Solidity: function changeController(_newController address) returns()
func (_Registry *RegistryTransactorSession) ChangeController(_newController common.Address) (*types.Transaction, error) {
return _Registry.Contract.ChangeController(&_Registry.TransactOpts, _newController)
}
// Remove is a paid mutator transaction binding the contract method 0x58edef4c.
//
// Solidity: function remove(a bytes) returns()
func (_Registry *RegistryTransactor) Remove(opts *bind.TransactOpts, a []byte) (*types.Transaction, error) {
return _Registry.contract.Transact(opts, "remove", a)
}
// Remove is a paid mutator transaction binding the contract method 0x58edef4c.
//
// Solidity: function remove(a bytes) returns()
func (_Registry *RegistrySession) Remove(a []byte) (*types.Transaction, error) {
return _Registry.Contract.Remove(&_Registry.TransactOpts, a)
}
// Remove is a paid mutator transaction binding the contract method 0x58edef4c.
//
// Solidity: function remove(a bytes) returns()
func (_Registry *RegistryTransactorSession) Remove(a []byte) (*types.Transaction, error) {
return _Registry.Contract.Remove(&_Registry.TransactOpts, a)
}
// RegistryMailServerAddedIterator is returned from FilterMailServerAdded and is used to iterate over the raw logs and unpacked data for MailServerAdded events raised by the Registry contract.
type RegistryMailServerAddedIterator struct {
Event *RegistryMailServerAdded // Event containing the contract specifics and raw log
contract *bind.BoundContract // Generic contract to use for unpacking event data
event string // Event name to use for unpacking event data
logs chan types.Log // Log channel receiving the found contract events
sub ethereum.Subscription // Subscription for errors, completion and termination
done bool // Whether the subscription completed delivering logs
fail error // Occurred error to stop iteration
}
// Next advances the iterator to the subsequent event, returning whether there
// are any more events found. In case of a retrieval or parsing error, false is
// returned and Error() can be queried for the exact failure.
func (it *RegistryMailServerAddedIterator) Next() bool {
// If the iterator failed, stop iterating
if it.fail != nil {
return false
}
// If the iterator completed, deliver directly whatever's available
if it.done {
select {
case log := <-it.logs:
it.Event = new(RegistryMailServerAdded)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
default:
return false
}
}
// Iterator still in progress, wait for either a data or an error event
select {
case log := <-it.logs:
it.Event = new(RegistryMailServerAdded)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
case err := <-it.sub.Err():
it.done = true
it.fail = err
return it.Next()
}
}
// Error returns any retrieval or parsing error occurred during filtering.
func (it *RegistryMailServerAddedIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *RegistryMailServerAddedIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// RegistryMailServerAdded represents a MailServerAdded event raised by the Registry contract.
type RegistryMailServerAdded struct {
A []byte
Raw types.Log // Blockchain specific contextual infos
}
// FilterMailServerAdded is a free log retrieval operation binding the contract event 0xcb379cb5890ec9889055734e1561cdc353a342d46d8d650c8c3a8d66383c29cd.
//
// Solidity: e MailServerAdded(a bytes)
func (_Registry *RegistryFilterer) FilterMailServerAdded(opts *bind.FilterOpts) (*RegistryMailServerAddedIterator, error) {
logs, sub, err := _Registry.contract.FilterLogs(opts, "MailServerAdded")
if err != nil {
return nil, err
}
return &RegistryMailServerAddedIterator{contract: _Registry.contract, event: "MailServerAdded", logs: logs, sub: sub}, nil
}
// WatchMailServerAdded is a free log subscription operation binding the contract event 0xcb379cb5890ec9889055734e1561cdc353a342d46d8d650c8c3a8d66383c29cd.
//
// Solidity: e MailServerAdded(a bytes)
func (_Registry *RegistryFilterer) WatchMailServerAdded(opts *bind.WatchOpts, sink chan<- *RegistryMailServerAdded) (event.Subscription, error) {
logs, sub, err := _Registry.contract.WatchLogs(opts, "MailServerAdded")
if err != nil {
return nil, err
}
return event.NewSubscription(func(quit <-chan struct{}) error {
defer sub.Unsubscribe()
for {
select {
case log := <-logs:
// New log arrived, parse the event and forward to the user
event := new(RegistryMailServerAdded)
if err := _Registry.contract.UnpackLog(event, "MailServerAdded", log); err != nil {
return err
}
event.Raw = log
select {
case sink <- event:
case err := <-sub.Err():
return err
case <-quit:
return nil
}
case err := <-sub.Err():
return err
case <-quit:
return nil
}
}
}), nil
}
// RegistryMailServerRemovedIterator is returned from FilterMailServerRemoved and is used to iterate over the raw logs and unpacked data for MailServerRemoved events raised by the Registry contract.
type RegistryMailServerRemovedIterator struct {
Event *RegistryMailServerRemoved // Event containing the contract specifics and raw log
contract *bind.BoundContract // Generic contract to use for unpacking event data
event string // Event name to use for unpacking event data
logs chan types.Log // Log channel receiving the found contract events
sub ethereum.Subscription // Subscription for errors, completion and termination
done bool // Whether the subscription completed delivering logs
fail error // Occurred error to stop iteration
}
// Next advances the iterator to the subsequent event, returning whether there
// are any more events found. In case of a retrieval or parsing error, false is
// returned and Error() can be queried for the exact failure.
func (it *RegistryMailServerRemovedIterator) Next() bool {
// If the iterator failed, stop iterating
if it.fail != nil {
return false
}
// If the iterator completed, deliver directly whatever's available
if it.done {
select {
case log := <-it.logs:
it.Event = new(RegistryMailServerRemoved)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
default:
return false
}
}
// Iterator still in progress, wait for either a data or an error event
select {
case log := <-it.logs:
it.Event = new(RegistryMailServerRemoved)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
case err := <-it.sub.Err():
it.done = true
it.fail = err
return it.Next()
}
}
// Error returns any retrieval or parsing error occurred during filtering.
func (it *RegistryMailServerRemovedIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *RegistryMailServerRemovedIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// RegistryMailServerRemoved represents a MailServerRemoved event raised by the Registry contract.
type RegistryMailServerRemoved struct {
A []byte
Raw types.Log // Blockchain specific contextual infos
}
// FilterMailServerRemoved is a free log retrieval operation binding the contract event 0x44e7d85a87eeb950b8bdc144d44b0b474be610d1953607251a0130edc10a222b.
//
// Solidity: e MailServerRemoved(a bytes)
func (_Registry *RegistryFilterer) FilterMailServerRemoved(opts *bind.FilterOpts) (*RegistryMailServerRemovedIterator, error) {
logs, sub, err := _Registry.contract.FilterLogs(opts, "MailServerRemoved")
if err != nil {
return nil, err
}
return &RegistryMailServerRemovedIterator{contract: _Registry.contract, event: "MailServerRemoved", logs: logs, sub: sub}, nil
}
// WatchMailServerRemoved is a free log subscription operation binding the contract event 0x44e7d85a87eeb950b8bdc144d44b0b474be610d1953607251a0130edc10a222b.
//
// Solidity: e MailServerRemoved(a bytes)
func (_Registry *RegistryFilterer) WatchMailServerRemoved(opts *bind.WatchOpts, sink chan<- *RegistryMailServerRemoved) (event.Subscription, error) {
logs, sub, err := _Registry.contract.WatchLogs(opts, "MailServerRemoved")
if err != nil {
return nil, err
}
return event.NewSubscription(func(quit <-chan struct{}) error {
defer sub.Unsubscribe()
for {
select {
case log := <-logs:
// New log arrived, parse the event and forward to the user
event := new(RegistryMailServerRemoved)
if err := _Registry.contract.UnpackLog(event, "MailServerRemoved", log); err != nil {
return err
}
event.Raw = log
select {
case sink <- event:
case err := <-sub.Err():
return err
case <-quit:
return nil
}
case err := <-sub.Err():
return err
case <-quit:
return nil
}
}
}), nil
}

View File

@ -0,0 +1,44 @@
package registry
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/discover"
)
var logger = log.New("package", "mailserver/registry")
// Verifier verifies nodes based on a smart contract.
type Verifier struct {
rc *RegistryCaller
}
// NewVerifier returns a new Verifier instance.
func NewVerifier(contractCaller bind.ContractCaller, contractAddress common.Address) (*Verifier, error) {
logger.Debug("initializing mailserver registry verifier", "address", fmt.Sprintf("0x%x", contractAddress))
rc, err := NewRegistryCaller(contractAddress, contractCaller)
if err != nil {
logger.Debug("error initializing mailserver registry verifier", "address", fmt.Sprintf("0x%x", contractAddress))
return nil, err
}
return &Verifier{
rc: rc,
}, nil
}
// VerifyNode checks if a given node is trusted using a smart contract.
func (v *Verifier) VerifyNode(ctx context.Context, nodeID discover.NodeID) bool {
res, err := v.rc.Exists(&bind.CallOpts{Context: ctx}, nodeID.Bytes())
logger.Debug("verifying node", "id", nodeID, "verified", res)
if err != nil {
logger.Error("error verifying node", "id", nodeID, "error", err)
return false
}
return res
}

View File

@ -0,0 +1,86 @@
package registry
import (
"context"
"crypto/ecdsa"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/stretchr/testify/suite"
)
type VerifierTestSuite struct {
suite.Suite
backend *backends.SimulatedBackend
privKey *ecdsa.PrivateKey
from common.Address
contractAddress common.Address
registry *Registry
verifier *Verifier
}
func TestVerifierTestSuite(t *testing.T) {
suite.Run(t, &VerifierTestSuite{})
}
func (s *VerifierTestSuite) SetupTest() {
s.setupAccount()
s.setupBackendAndContract()
var err error
s.verifier, err = NewVerifier(s.backend, s.contractAddress)
s.Require().NoError(err)
}
func (s *VerifierTestSuite) setupBackendAndContract() {
var err error
auth := bind.NewKeyedTransactor(s.privKey)
alloc := make(core.GenesisAlloc)
alloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(133700000)}
s.backend = backends.NewSimulatedBackend(alloc)
s.contractAddress, _, s.registry, err = DeployRegistry(auth, s.backend)
s.Require().NoError(err)
s.backend.Commit()
}
func (s *VerifierTestSuite) setupAccount() {
var err error
s.privKey, err = crypto.GenerateKey()
s.Require().NoError(err)
s.from = crypto.PubkeyToAddress(s.privKey.PublicKey)
}
func (s *VerifierTestSuite) add(nodeID discover.NodeID) {
auth := bind.NewKeyedTransactor(s.privKey)
_, err := s.registry.Add(auth, nodeID[:])
s.Require().NoError(err)
s.backend.Commit()
}
func (s *VerifierTestSuite) generateNodeID() discover.NodeID {
k, err := crypto.GenerateKey()
s.Require().NoError(err)
return discover.PubkeyID(&k.PublicKey)
}
func (s *VerifierTestSuite) TestVerifyNode() {
id := s.generateNodeID()
res := s.verifier.VerifyNode(context.Background(), id)
s.Require().False(res)
s.add(id)
res = s.verifier.VerifyNode(context.Background(), id)
s.Require().True(res)
}

View File

@ -251,6 +251,9 @@ func (n *StatusNode) startDiscovery() error {
// TODO(dshulyak) consider adding a flag to define this behaviour // TODO(dshulyak) consider adding a flag to define this behaviour
options.AllowStop = len(n.config.RegisterTopics) == 0 options.AllowStop = len(n.config.RegisterTopics) == 0
options.TrustedMailServers = parseNodesToNodeID(n.config.ClusterConfig.TrustedMailServers) options.TrustedMailServers = parseNodesToNodeID(n.config.ClusterConfig.TrustedMailServers)
options.MailServerRegistryAddress = n.config.MailServerRegistryAddress
n.peerPool = peers.NewPeerPool( n.peerPool = peers.NewPeerPool(
n.discovery, n.discovery,
n.config.RequireTopics, n.config.RequireTopics,
@ -263,7 +266,7 @@ func (n *StatusNode) startDiscovery() error {
if err := n.register.Start(); err != nil { if err := n.register.Start(); err != nil {
return err return err
} }
return n.peerPool.Start(n.gethNode.Server()) return n.peerPool.Start(n.gethNode.Server(), n.rpcClient)
} }
// Stop will stop current StatusNode. A stopped node cannot be resumed. // Stop will stop current StatusNode. A stopped node cannot be resumed.
@ -565,8 +568,8 @@ func (n *StatusNode) AccountKeyStore() (*keystore.KeyStore, error) {
// RPCClient exposes reference to RPC client connected to the running node. // RPCClient exposes reference to RPC client connected to the running node.
func (n *StatusNode) RPCClient() *rpc.Client { func (n *StatusNode) RPCClient() *rpc.Client {
n.mu.Lock() n.mu.RLock()
defer n.mu.Unlock() defer n.mu.RUnlock()
return n.rpcClient return n.rpcClient
} }

View File

@ -357,6 +357,9 @@ type NodeConfig struct {
// DebugAPIEnabled enables debug api // DebugAPIEnabled enables debug api
DebugAPIEnabled bool DebugAPIEnabled bool
// MailServerRegistryAddress is the MailServerRegistry contract address
MailServerRegistryAddress string
} }
// NewNodeConfig creates new node configuration object // NewNodeConfig creates new node configuration object

View File

@ -5,6 +5,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -12,7 +13,9 @@ import (
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/discv5"
"github.com/status-im/status-go/contracts"
"github.com/status-im/status-go/discovery" "github.com/status-im/status-go/discovery"
"github.com/status-im/status-go/mailserver/registry"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/status-im/status-go/peers/verifier" "github.com/status-im/status-go/peers/verifier"
"github.com/status-im/status-go/signal" "github.com/status-im/status-go/signal"
@ -56,6 +59,8 @@ type Options struct {
TopicStopSearchDelay time.Duration TopicStopSearchDelay time.Duration
// TrustedMailServers is a list of trusted nodes. // TrustedMailServers is a list of trusted nodes.
TrustedMailServers []discover.NodeID TrustedMailServers []discover.NodeID
// MailServerRegistryAddress is the MailServerRegistry contract address
MailServerRegistryAddress string
} }
// NewDefaultOptions returns a struct with default Options. // NewDefaultOptions returns a struct with default Options.
@ -115,7 +120,7 @@ func (p *PeerPool) setDiscoveryTimeout() {
} }
// Start creates topic pool for each topic in config and subscribes to server events. // Start creates topic pool for each topic in config and subscribes to server events.
func (p *PeerPool) Start(server *p2p.Server) error { func (p *PeerPool) Start(server *p2p.Server, rpcClient contracts.RPCClient) error {
if !p.discovery.Running() { if !p.discovery.Running() {
return ErrDiscv5NotRunning return ErrDiscv5NotRunning
} }
@ -143,7 +148,11 @@ func (p *PeerPool) Start(server *p2p.Server) error {
var topicPool TopicPoolInterface var topicPool TopicPoolInterface
t := newTopicPool(p.discovery, topic, limits, p.opts.SlowSync, p.opts.FastSync, p.cache) t := newTopicPool(p.discovery, topic, limits, p.opts.SlowSync, p.opts.FastSync, p.cache)
if topic == MailServerDiscoveryTopic { if topic == MailServerDiscoveryTopic {
topicPool = newCacheOnlyTopicPool(t, verifier.NewLocalVerifier(p.opts.TrustedMailServers)) v, err := p.initVerifier(rpcClient)
if err != nil {
return err
}
topicPool = newCacheOnlyTopicPool(t, v)
} else { } else {
topicPool = t topicPool = t
} }
@ -159,6 +168,16 @@ func (p *PeerPool) Start(server *p2p.Server) error {
return nil return nil
} }
func (p *PeerPool) initVerifier(rpcClient contracts.RPCClient) (v Verifier, err error) {
if addr := p.opts.MailServerRegistryAddress; addr != "" {
caller := contracts.NewContractCaller(rpcClient)
addrBytes := common.FromHex(addr)
return registry.NewVerifier(caller, common.BytesToAddress(addrBytes))
}
return verifier.NewLocalVerifier(p.opts.TrustedMailServers), nil
}
func (p *PeerPool) startDiscovery() error { func (p *PeerPool) startDiscovery() error {
if p.discovery.Running() { if p.discovery.Running() {
return nil return nil

View File

@ -162,13 +162,13 @@ func (s *PeerPoolSimulationSuite) TestPeerPoolCacheEthV5() {
config := map[discv5.Topic]params.Limits{ config := map[discv5.Topic]params.Limits{
topic: params.NewLimits(1, 1), topic: params.NewLimits(1, 1),
} }
peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 100 * time.Millisecond, nil} peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 100 * time.Millisecond, nil, ""}
cache, err := newInMemoryCache() cache, err := newInMemoryCache()
s.Require().NoError(err) s.Require().NoError(err)
peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts) peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts)
// start peer pool // start peer pool
s.Require().NoError(peerPool.Start(s.peers[1])) s.Require().NoError(peerPool.Start(s.peers[1], nil))
defer peerPool.Stop() defer peerPool.Stop()
// check if cache is passed to topic pools // check if cache is passed to topic pools
@ -220,7 +220,7 @@ func (s *PeerPoolSimulationSuite) singleTopicDiscoveryWithFailover() {
config := map[discv5.Topic]params.Limits{ config := map[discv5.Topic]params.Limits{
topic: params.NewLimits(1, 1), // limits are chosen for simplicity of the simulation topic: params.NewLimits(1, 1), // limits are chosen for simplicity of the simulation
} }
peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 0, nil} peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 0, nil, ""}
cache, err := newInMemoryCache() cache, err := newInMemoryCache()
s.Require().NoError(err) s.Require().NoError(err)
peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts) peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts)
@ -235,7 +235,7 @@ func (s *PeerPoolSimulationSuite) singleTopicDiscoveryWithFailover() {
defer subscription.Unsubscribe() defer subscription.Unsubscribe()
// start the peer pool // start the peer pool
s.Require().NoError(peerPool.Start(s.peers[1])) s.Require().NoError(peerPool.Start(s.peers[1], nil))
defer peerPool.Stop() defer peerPool.Stop()
s.Equal(signal.EventDiscoveryStarted, s.getPoolEvent(poolEvents)) s.Equal(signal.EventDiscoveryStarted, s.getPoolEvent(poolEvents))
@ -302,9 +302,9 @@ func TestPeerPoolMaxPeersOverflow(t *testing.T) {
defer func() { assert.NoError(t, discovery.Stop()) }() defer func() { assert.NoError(t, discovery.Stop()) }()
require.True(t, discovery.Running()) require.True(t, discovery.Running())
poolOpts := &Options{DefaultFastSync, DefaultSlowSync, 0, true, 100 * time.Millisecond, nil} poolOpts := &Options{DefaultFastSync, DefaultSlowSync, 0, true, 100 * time.Millisecond, nil, ""}
pool := NewPeerPool(discovery, nil, nil, poolOpts) pool := NewPeerPool(discovery, nil, nil, poolOpts)
require.NoError(t, pool.Start(peer)) require.NoError(t, pool.Start(peer, nil))
require.Equal(t, signal.EventDiscoveryStarted, <-signals) require.Equal(t, signal.EventDiscoveryStarted, <-signals)
// without config, it will stop the discovery because all topic pools are satisfied // without config, it will stop the discovery because all topic pools are satisfied
pool.events <- &p2p.PeerEvent{Type: p2p.PeerEventTypeAdd} pool.events <- &p2p.PeerEvent{Type: p2p.PeerEventTypeAdd}
@ -355,9 +355,9 @@ func TestPeerPoolDiscV5Timeout(t *testing.T) {
require.True(t, discovery.Running()) require.True(t, discovery.Running())
// start PeerPool // start PeerPool
poolOpts := &Options{DefaultFastSync, DefaultSlowSync, time.Millisecond * 100, true, 100 * time.Millisecond, nil} poolOpts := &Options{DefaultFastSync, DefaultSlowSync, time.Millisecond * 100, true, 100 * time.Millisecond, nil, ""}
pool := NewPeerPool(discovery, nil, nil, poolOpts) pool := NewPeerPool(discovery, nil, nil, poolOpts)
require.NoError(t, pool.Start(server)) require.NoError(t, pool.Start(server, nil))
require.Equal(t, signal.EventDiscoveryStarted, <-signals) require.Equal(t, signal.EventDiscoveryStarted, <-signals)
// timeout after finding no peers // timeout after finding no peers
@ -402,9 +402,9 @@ func TestPeerPoolNotAllowedStopping(t *testing.T) {
require.True(t, discovery.Running()) require.True(t, discovery.Running())
// start PeerPool // start PeerPool
poolOpts := &Options{DefaultFastSync, DefaultSlowSync, time.Millisecond * 100, false, 100 * time.Millisecond, nil} poolOpts := &Options{DefaultFastSync, DefaultSlowSync, time.Millisecond * 100, false, 100 * time.Millisecond, nil, ""}
pool := NewPeerPool(discovery, nil, nil, poolOpts) pool := NewPeerPool(discovery, nil, nil, poolOpts)
require.NoError(t, pool.Start(server)) require.NoError(t, pool.Start(server, nil))
// wait 2x timeout duration // wait 2x timeout duration
<-time.After(pool.opts.DiscServerTimeout * 2) <-time.After(pool.opts.DiscServerTimeout * 2)
@ -419,13 +419,13 @@ func (s *PeerPoolSimulationSuite) TestUpdateTopicLimits() {
config := map[discv5.Topic]params.Limits{ config := map[discv5.Topic]params.Limits{
topic: params.NewLimits(1, 1), topic: params.NewLimits(1, 1),
} }
peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 100 * time.Millisecond, nil} peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 100 * time.Millisecond, nil, ""}
cache, err := newInMemoryCache() cache, err := newInMemoryCache()
s.Require().NoError(err) s.Require().NoError(err)
peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts) peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts)
// start peer pool // start peer pool
s.Require().NoError(peerPool.Start(s.peers[1])) s.Require().NoError(peerPool.Start(s.peers[1], nil))
defer peerPool.Stop() defer peerPool.Stop()
for _, topicPool := range peerPool.topics { for _, topicPool := range peerPool.topics {
@ -495,9 +495,10 @@ func (s *PeerPoolSimulationSuite) TestMailServerPeersDiscovery() {
true, true,
100 * time.Millisecond, 100 * time.Millisecond,
[]discover.NodeID{s.peers[0].Self().ID}, []discover.NodeID{s.peers[0].Self().ID},
"",
} }
peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts) peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts)
s.Require().NoError(peerPool.Start(s.peers[1])) s.Require().NoError(peerPool.Start(s.peers[1], nil))
defer peerPool.Stop() defer peerPool.Stop()
// wait for and verify the mail server peer // wait for and verify the mail server peer