diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index fa8828f61b..336c59e5b9 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -405,6 +405,28 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { return nil } +// ShiftBlocks inserts a batch of empty blocks to blockchain. +func (b *SimulatedBackend) ShiftBlocks(number int) error { + b.mu.Lock() + defer b.mu.Unlock() + // Insert a batch of empty blocks and commit to the database + blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, number, func(i int, block *core.BlockGen) {}) + if _, err := b.blockchain.InsertChain(blocks); err != nil { + panic(err) // This cannot happen unless the simulator is wrong, fail in that case + } + // Apply all pending transactions to new pending blocks. + blocks, _ = core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + for _, tx := range b.pendingBlock.Transactions() { + block.AddTx(tx) + } + }) + + statedb, _ := b.blockchain.State() + b.pendingBlock = blocks[0] + b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database()) + return nil +} + // callmsg implements core.Message to allow passing it as a transaction simulator. type callmsg struct { ethereum.CallMsg diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 522ad06b61..db1d0ba420 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -19,11 +19,13 @@ package utils import ( "crypto/ecdsa" + "errors" "fmt" "io/ioutil" "math/big" "os" "path/filepath" + "reflect" "runtime" "strconv" "strings" @@ -44,6 +46,7 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/les" @@ -1175,6 +1178,22 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { } return fullNode, err }) + if err == nil { + err = stack.RegisterCallback(reflect.TypeOf(ð.Ethereum{}), func(service node.Service) error { + if e, ok := service.(*eth.Ethereum); ok { + // The node lock will be held during the whole node setup procedure, so no extra + // lock operation is needed. + rpcClient, err := stack.AttachLocked() + if err != nil { + return err + } + e.SetClient(ethclient.NewClient(rpcClient)) + return nil + } else { + return errors.New("the service given is not the required type") + } + }) + } } if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) diff --git a/contracts/registrar/contract/registrar.go b/contracts/registrar/contract/registrar.go new file mode 100644 index 0000000000..543a5fc643 --- /dev/null +++ b/contracts/registrar/contract/registrar.go @@ -0,0 +1,704 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package contract + +import ( + "math/big" + "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" +) + +// ContractABI is the input ABI used to generate the binding from. +const ContractABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"_addr\",\"type\":\"address\"},{\"name\":\"_description\",\"type\":\"string\"}],\"name\":\"AddAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"GetAllAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"GetLatestCheckpoint\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"},{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_sectionIndex\",\"type\":\"uint256\"}],\"name\":\"GetCheckpoint\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_addr\",\"type\":\"address\"},{\"name\":\"_reason\",\"type\":\"string\"}],\"name\":\"RemoveAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_sectionIndex\",\"type\":\"uint256\"},{\"name\":\"_hash\",\"type\":\"bytes32\"}],\"name\":\"SetCheckpoint\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_adminlist\",\"type\":\"address[]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"index\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"grantor\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"checkpointHash\",\"type\":\"bytes32\"}],\"name\":\"NewCheckpointEvent\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"addr\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"grantor\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"description\",\"type\":\"string\"}],\"name\":\"AddAdminEvent\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"addr\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"grantor\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"reason\",\"type\":\"string\"}],\"name\":\"RemoveAdminEvent\",\"type\":\"event\"}]" + +// ContractBin is the compiled bytecode used for deploying new contracts. +const ContractBin = `608060405234801561001057600080fd5b50604051610de2380380610de283398101806040528101908080518201929190505050600060016000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555060013390806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050600090505b81518110156101d2576001600080848481518110151561010057fe5b9060200190602002015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506001828281518110151561015857fe5b9060200190602002015190806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505080806001019150506100e4565b5050610bff806101e36000396000f300608060405260043610610078576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633561247d1461007d57806345848dfc1461011e5780634d6a304c1461018a578063710aeac8146101c4578063a5ba0be21461020d578063c4885d57146102ae575b600080fd5b34801561008957600080fd5b50610104600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290505050610301565b604051808215151515815260200191505060405180910390f35b34801561012a57600080fd5b50610133610558565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561017657808201518184015260208101905061015b565b505050509050019250505060405180910390f35b34801561019657600080fd5b5061019f61063f565b6040518083815260200182600019166000191681526020019250505060405180910390f35b3480156101d057600080fd5b506101ef6004803603810190808035906020019092919050505061065e565b60405180826000191660001916815260200191505060405180910390f35b34801561021957600080fd5b50610294600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919291929050505061067b565b604051808215151515815260200191505060405180910390f35b3480156102ba57600080fd5b506102e7600480360381019080803590602001909291908035600019169060200190929190505050610a0d565b604051808215151515815260200191505060405180910390f35b6000806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205411151561034f57600080fd5b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054111561039f5760009050610552565b60016000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555060018390806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550507fecd88a3d10808aac9f879bbae95f9e4f9687f6c9f0615b88af2085f226d3cf93833384604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001828103825283818151815260200191508051906020019080838360005b838110156105115780820151818401526020810190506104f6565b50505050905090810190601f16801561053e5780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a1600190505b92915050565b60608060006001805490506040519080825280602002602001820160405280156105915781602001602082028038833980820191505090505b509150600090505b600180549050811015610637576001818154811015156105b557fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1682828151811015156105ee57fe5b9060200190602002019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250508080600101915050610599565b819250505090565b600080600061064f60035461065e565b90506003548192509250509091565b600060026000838152602001908152602001600020549050919050565b6000806000806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541115156106cc57600080fd5b60008060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054141561071c5760009250610a05565b6000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009055600091505b6001805490508210156108fc578473ffffffffffffffffffffffffffffffffffffffff1660018381548110151561079657fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614156108ef578190505b600180805490500381101561089057600180820181548110151561080157fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1660018281548110151561083b57fe5b9060005260206000200160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080806001019150506107e1565b6001808080549050038154811015156108a557fe5b9060005260206000200160006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055600180818180549050039150816108e99190610b82565b506108fc565b8180600101925050610763565b7fe7d6ab069cde0507dc9c5971bf93e9fd183db04690725946e8cae907367e7c1f853386604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001828103825283818151815260200191508051906020019080838360005b838110156109c45780820151818401526020810190506109a9565b50505050905090810190601f1680156109f15780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a1600192505b505092915050565b6000806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054111515610a5b57600080fd5b6003548314158015610a7257506001600354018314155b8015610a815750600060035414155b15610a8f5760009050610b7c565b610100618000600185010201431015610aab5760009050610b7c565b600060035414158015610abf575060035483145b8015610ad657506120006180006001850102014310155b15610ae45760009050610b7c565b8160026000858152602001908152602001600020816000191690555082600381905550827f5b3eab050ccf1a983a26b4f6acb13c199093c7d2e7523a0ff036519f7ddaf5603384604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182600019166000191681526020019250505060405180910390a25b92915050565b815481835581811115610ba957818360005260206000209182019101610ba89190610bae565b5b505050565b610bd091905b80821115610bcc576000816000905550600101610bb4565b5090565b905600a165627a7a723058206af229b85ceb246e4b06af39c8058d670504df652a68b19dad6b2a883f3028580029` + +// DeployContract deploys a new Ethereum contract, binding an instance of Contract to it. +func DeployContract(auth *bind.TransactOpts, backend bind.ContractBackend, _adminlist []common.Address) (common.Address, *types.Transaction, *Contract, error) { + parsed, err := abi.JSON(strings.NewReader(ContractABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ContractBin), backend, _adminlist) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Contract{ContractCaller: ContractCaller{contract: contract}, ContractTransactor: ContractTransactor{contract: contract}, ContractFilterer: ContractFilterer{contract: contract}}, nil +} + +// Contract is an auto generated Go binding around an Ethereum contract. +type Contract struct { + ContractCaller // Read-only binding to the contract + ContractTransactor // Write-only binding to the contract + ContractFilterer // Log filterer for contract events +} + +// ContractCaller is an auto generated read-only Go binding around an Ethereum contract. +type ContractCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ContractTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ContractTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ContractFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ContractFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ContractSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ContractSession struct { + Contract *Contract // 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 +} + +// ContractCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ContractCallerSession struct { + Contract *ContractCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ContractTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ContractTransactorSession struct { + Contract *ContractTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ContractRaw is an auto generated low-level Go binding around an Ethereum contract. +type ContractRaw struct { + Contract *Contract // Generic contract binding to access the raw methods on +} + +// ContractCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ContractCallerRaw struct { + Contract *ContractCaller // Generic read-only contract binding to access the raw methods on +} + +// ContractTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ContractTransactorRaw struct { + Contract *ContractTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewContract creates a new instance of Contract, bound to a specific deployed contract. +func NewContract(address common.Address, backend bind.ContractBackend) (*Contract, error) { + contract, err := bindContract(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Contract{ContractCaller: ContractCaller{contract: contract}, ContractTransactor: ContractTransactor{contract: contract}, ContractFilterer: ContractFilterer{contract: contract}}, nil +} + +// NewContractCaller creates a new read-only instance of Contract, bound to a specific deployed contract. +func NewContractCaller(address common.Address, caller bind.ContractCaller) (*ContractCaller, error) { + contract, err := bindContract(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ContractCaller{contract: contract}, nil +} + +// NewContractTransactor creates a new write-only instance of Contract, bound to a specific deployed contract. +func NewContractTransactor(address common.Address, transactor bind.ContractTransactor) (*ContractTransactor, error) { + contract, err := bindContract(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ContractTransactor{contract: contract}, nil +} + +// NewContractFilterer creates a new log filterer instance of Contract, bound to a specific deployed contract. +func NewContractFilterer(address common.Address, filterer bind.ContractFilterer) (*ContractFilterer, error) { + contract, err := bindContract(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ContractFilterer{contract: contract}, nil +} + +// bindContract binds a generic wrapper to an already deployed contract. +func bindContract(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ContractABI)) + 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 (_Contract *ContractRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Contract.Contract.ContractCaller.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 (_Contract *ContractRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Contract.Contract.ContractTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Contract *ContractRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Contract.Contract.ContractTransactor.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 (_Contract *ContractCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Contract.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 (_Contract *ContractTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Contract.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Contract *ContractTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Contract.Contract.contract.Transact(opts, method, params...) +} + +// GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc. +// +// Solidity: function GetAllAdmin() constant returns(address[]) +func (_Contract *ContractCaller) GetAllAdmin(opts *bind.CallOpts) ([]common.Address, error) { + var ( + ret0 = new([]common.Address) + ) + out := ret0 + err := _Contract.contract.Call(opts, out, "GetAllAdmin") + return *ret0, err +} + +// GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc. +// +// Solidity: function GetAllAdmin() constant returns(address[]) +func (_Contract *ContractSession) GetAllAdmin() ([]common.Address, error) { + return _Contract.Contract.GetAllAdmin(&_Contract.CallOpts) +} + +// GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc. +// +// Solidity: function GetAllAdmin() constant returns(address[]) +func (_Contract *ContractCallerSession) GetAllAdmin() ([]common.Address, error) { + return _Contract.Contract.GetAllAdmin(&_Contract.CallOpts) +} + +// GetCheckpoint is a free data retrieval call binding the contract method 0x710aeac8. +// +// Solidity: function GetCheckpoint(_sectionIndex uint256) constant returns(bytes32) +func (_Contract *ContractCaller) GetCheckpoint(opts *bind.CallOpts, _sectionIndex *big.Int) ([32]byte, error) { + var ( + ret0 = new([32]byte) + ) + out := ret0 + err := _Contract.contract.Call(opts, out, "GetCheckpoint", _sectionIndex) + return *ret0, err +} + +// GetCheckpoint is a free data retrieval call binding the contract method 0x710aeac8. +// +// Solidity: function GetCheckpoint(_sectionIndex uint256) constant returns(bytes32) +func (_Contract *ContractSession) GetCheckpoint(_sectionIndex *big.Int) ([32]byte, error) { + return _Contract.Contract.GetCheckpoint(&_Contract.CallOpts, _sectionIndex) +} + +// GetCheckpoint is a free data retrieval call binding the contract method 0x710aeac8. +// +// Solidity: function GetCheckpoint(_sectionIndex uint256) constant returns(bytes32) +func (_Contract *ContractCallerSession) GetCheckpoint(_sectionIndex *big.Int) ([32]byte, error) { + return _Contract.Contract.GetCheckpoint(&_Contract.CallOpts, _sectionIndex) +} + +// GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c. +// +// Solidity: function GetLatestCheckpoint() constant returns(uint256, bytes32) +func (_Contract *ContractCaller) GetLatestCheckpoint(opts *bind.CallOpts) (*big.Int, [32]byte, error) { + var ( + ret0 = new(*big.Int) + ret1 = new([32]byte) + ) + out := &[]interface{}{ + ret0, + ret1, + } + err := _Contract.contract.Call(opts, out, "GetLatestCheckpoint") + return *ret0, *ret1, err +} + +// GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c. +// +// Solidity: function GetLatestCheckpoint() constant returns(uint256, bytes32) +func (_Contract *ContractSession) GetLatestCheckpoint() (*big.Int, [32]byte, error) { + return _Contract.Contract.GetLatestCheckpoint(&_Contract.CallOpts) +} + +// GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c. +// +// Solidity: function GetLatestCheckpoint() constant returns(uint256, bytes32) +func (_Contract *ContractCallerSession) GetLatestCheckpoint() (*big.Int, [32]byte, error) { + return _Contract.Contract.GetLatestCheckpoint(&_Contract.CallOpts) +} + +// AddAdmin is a paid mutator transaction binding the contract method 0x3561247d. +// +// Solidity: function AddAdmin(_addr address, _description string) returns(bool) +func (_Contract *ContractTransactor) AddAdmin(opts *bind.TransactOpts, _addr common.Address, _description string) (*types.Transaction, error) { + return _Contract.contract.Transact(opts, "AddAdmin", _addr, _description) +} + +// AddAdmin is a paid mutator transaction binding the contract method 0x3561247d. +// +// Solidity: function AddAdmin(_addr address, _description string) returns(bool) +func (_Contract *ContractSession) AddAdmin(_addr common.Address, _description string) (*types.Transaction, error) { + return _Contract.Contract.AddAdmin(&_Contract.TransactOpts, _addr, _description) +} + +// AddAdmin is a paid mutator transaction binding the contract method 0x3561247d. +// +// Solidity: function AddAdmin(_addr address, _description string) returns(bool) +func (_Contract *ContractTransactorSession) AddAdmin(_addr common.Address, _description string) (*types.Transaction, error) { + return _Contract.Contract.AddAdmin(&_Contract.TransactOpts, _addr, _description) +} + +// RemoveAdmin is a paid mutator transaction binding the contract method 0xa5ba0be2. +// +// Solidity: function RemoveAdmin(_addr address, _reason string) returns(bool) +func (_Contract *ContractTransactor) RemoveAdmin(opts *bind.TransactOpts, _addr common.Address, _reason string) (*types.Transaction, error) { + return _Contract.contract.Transact(opts, "RemoveAdmin", _addr, _reason) +} + +// RemoveAdmin is a paid mutator transaction binding the contract method 0xa5ba0be2. +// +// Solidity: function RemoveAdmin(_addr address, _reason string) returns(bool) +func (_Contract *ContractSession) RemoveAdmin(_addr common.Address, _reason string) (*types.Transaction, error) { + return _Contract.Contract.RemoveAdmin(&_Contract.TransactOpts, _addr, _reason) +} + +// RemoveAdmin is a paid mutator transaction binding the contract method 0xa5ba0be2. +// +// Solidity: function RemoveAdmin(_addr address, _reason string) returns(bool) +func (_Contract *ContractTransactorSession) RemoveAdmin(_addr common.Address, _reason string) (*types.Transaction, error) { + return _Contract.Contract.RemoveAdmin(&_Contract.TransactOpts, _addr, _reason) +} + +// SetCheckpoint is a paid mutator transaction binding the contract method 0xc4885d57. +// +// Solidity: function SetCheckpoint(_sectionIndex uint256, _hash bytes32) returns(bool) +func (_Contract *ContractTransactor) SetCheckpoint(opts *bind.TransactOpts, _sectionIndex *big.Int, _hash [32]byte) (*types.Transaction, error) { + return _Contract.contract.Transact(opts, "SetCheckpoint", _sectionIndex, _hash) +} + +// SetCheckpoint is a paid mutator transaction binding the contract method 0xc4885d57. +// +// Solidity: function SetCheckpoint(_sectionIndex uint256, _hash bytes32) returns(bool) +func (_Contract *ContractSession) SetCheckpoint(_sectionIndex *big.Int, _hash [32]byte) (*types.Transaction, error) { + return _Contract.Contract.SetCheckpoint(&_Contract.TransactOpts, _sectionIndex, _hash) +} + +// SetCheckpoint is a paid mutator transaction binding the contract method 0xc4885d57. +// +// Solidity: function SetCheckpoint(_sectionIndex uint256, _hash bytes32) returns(bool) +func (_Contract *ContractTransactorSession) SetCheckpoint(_sectionIndex *big.Int, _hash [32]byte) (*types.Transaction, error) { + return _Contract.Contract.SetCheckpoint(&_Contract.TransactOpts, _sectionIndex, _hash) +} + +// ContractAddAdminEventIterator is returned from FilterAddAdminEvent and is used to iterate over the raw logs and unpacked data for AddAdminEvent events raised by the Contract contract. +type ContractAddAdminEventIterator struct { + Event *ContractAddAdminEvent // 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 *ContractAddAdminEventIterator) 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(ContractAddAdminEvent) + 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(ContractAddAdminEvent) + 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 *ContractAddAdminEventIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractAddAdminEventIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractAddAdminEvent represents a AddAdminEvent event raised by the Contract contract. +type ContractAddAdminEvent struct { + Addr common.Address + Grantor common.Address + Description string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAddAdminEvent is a free log retrieval operation binding the contract event 0xecd88a3d10808aac9f879bbae95f9e4f9687f6c9f0615b88af2085f226d3cf93. +// +// Solidity: e AddAdminEvent(addr address, grantor address, description string) +func (_Contract *ContractFilterer) FilterAddAdminEvent(opts *bind.FilterOpts) (*ContractAddAdminEventIterator, error) { + + logs, sub, err := _Contract.contract.FilterLogs(opts, "AddAdminEvent") + if err != nil { + return nil, err + } + return &ContractAddAdminEventIterator{contract: _Contract.contract, event: "AddAdminEvent", logs: logs, sub: sub}, nil +} + +// WatchAddAdminEvent is a free log subscription operation binding the contract event 0xecd88a3d10808aac9f879bbae95f9e4f9687f6c9f0615b88af2085f226d3cf93. +// +// Solidity: e AddAdminEvent(addr address, grantor address, description string) +func (_Contract *ContractFilterer) WatchAddAdminEvent(opts *bind.WatchOpts, sink chan<- *ContractAddAdminEvent) (event.Subscription, error) { + + logs, sub, err := _Contract.contract.WatchLogs(opts, "AddAdminEvent") + 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(ContractAddAdminEvent) + if err := _Contract.contract.UnpackLog(event, "AddAdminEvent", 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 +} + +// ContractNewCheckpointEventIterator is returned from FilterNewCheckpointEvent and is used to iterate over the raw logs and unpacked data for NewCheckpointEvent events raised by the Contract contract. +type ContractNewCheckpointEventIterator struct { + Event *ContractNewCheckpointEvent // 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 *ContractNewCheckpointEventIterator) 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(ContractNewCheckpointEvent) + 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(ContractNewCheckpointEvent) + 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 *ContractNewCheckpointEventIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractNewCheckpointEventIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractNewCheckpointEvent represents a NewCheckpointEvent event raised by the Contract contract. +type ContractNewCheckpointEvent struct { + Index *big.Int + Grantor common.Address + CheckpointHash [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNewCheckpointEvent is a free log retrieval operation binding the contract event 0x5b3eab050ccf1a983a26b4f6acb13c199093c7d2e7523a0ff036519f7ddaf560. +// +// Solidity: e NewCheckpointEvent(index indexed uint256, grantor address, checkpointHash bytes32) +func (_Contract *ContractFilterer) FilterNewCheckpointEvent(opts *bind.FilterOpts, index []*big.Int) (*ContractNewCheckpointEventIterator, error) { + + var indexRule []interface{} + for _, indexItem := range index { + indexRule = append(indexRule, indexItem) + } + + logs, sub, err := _Contract.contract.FilterLogs(opts, "NewCheckpointEvent", indexRule) + if err != nil { + return nil, err + } + return &ContractNewCheckpointEventIterator{contract: _Contract.contract, event: "NewCheckpointEvent", logs: logs, sub: sub}, nil +} + +// WatchNewCheckpointEvent is a free log subscription operation binding the contract event 0x5b3eab050ccf1a983a26b4f6acb13c199093c7d2e7523a0ff036519f7ddaf560. +// +// Solidity: e NewCheckpointEvent(index indexed uint256, grantor address, checkpointHash bytes32) +func (_Contract *ContractFilterer) WatchNewCheckpointEvent(opts *bind.WatchOpts, sink chan<- *ContractNewCheckpointEvent, index []*big.Int) (event.Subscription, error) { + + var indexRule []interface{} + for _, indexItem := range index { + indexRule = append(indexRule, indexItem) + } + + logs, sub, err := _Contract.contract.WatchLogs(opts, "NewCheckpointEvent", indexRule) + 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(ContractNewCheckpointEvent) + if err := _Contract.contract.UnpackLog(event, "NewCheckpointEvent", 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 +} + +// ContractRemoveAdminEventIterator is returned from FilterRemoveAdminEvent and is used to iterate over the raw logs and unpacked data for RemoveAdminEvent events raised by the Contract contract. +type ContractRemoveAdminEventIterator struct { + Event *ContractRemoveAdminEvent // 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 *ContractRemoveAdminEventIterator) 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(ContractRemoveAdminEvent) + 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(ContractRemoveAdminEvent) + 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 *ContractRemoveAdminEventIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractRemoveAdminEventIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractRemoveAdminEvent represents a RemoveAdminEvent event raised by the Contract contract. +type ContractRemoveAdminEvent struct { + Addr common.Address + Grantor common.Address + Reason string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRemoveAdminEvent is a free log retrieval operation binding the contract event 0xe7d6ab069cde0507dc9c5971bf93e9fd183db04690725946e8cae907367e7c1f. +// +// Solidity: e RemoveAdminEvent(addr address, grantor address, reason string) +func (_Contract *ContractFilterer) FilterRemoveAdminEvent(opts *bind.FilterOpts) (*ContractRemoveAdminEventIterator, error) { + + logs, sub, err := _Contract.contract.FilterLogs(opts, "RemoveAdminEvent") + if err != nil { + return nil, err + } + return &ContractRemoveAdminEventIterator{contract: _Contract.contract, event: "RemoveAdminEvent", logs: logs, sub: sub}, nil +} + +// WatchRemoveAdminEvent is a free log subscription operation binding the contract event 0xe7d6ab069cde0507dc9c5971bf93e9fd183db04690725946e8cae907367e7c1f. +// +// Solidity: e RemoveAdminEvent(addr address, grantor address, reason string) +func (_Contract *ContractFilterer) WatchRemoveAdminEvent(opts *bind.WatchOpts, sink chan<- *ContractRemoveAdminEvent) (event.Subscription, error) { + + logs, sub, err := _Contract.contract.WatchLogs(opts, "RemoveAdminEvent") + 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(ContractRemoveAdminEvent) + if err := _Contract.contract.UnpackLog(event, "RemoveAdminEvent", 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 +} diff --git a/contracts/registrar/contract/registrar.sol b/contracts/registrar/contract/registrar.sol new file mode 100644 index 0000000000..7dbbfe8e6d --- /dev/null +++ b/contracts/registrar/contract/registrar.sol @@ -0,0 +1,217 @@ +pragma solidity ^0.4.24; + +/** + * @title Registrar + * @author Gary Rong + * @dev Implementation of the blockchain checkpoint information registrar. + */ +contract Registrar { + /* + Modifiers + */ + + /** + * @dev Check whether the message sender is authorized. + */ + modifier OnlyAuthorized() { + require(admins[msg.sender] > 0); + _; + } + + /* + Events + */ + + // NewCheckpointEvent is emitted when new checkpoint is registered. + // Grantor indicates the people register the checkpoint. + // We use checkpoint hash instead of the full checkpoint to make the transaction cheaper. + event NewCheckpointEvent(uint indexed index, address grantor, bytes32 checkpointHash); + + // AddAdminEvent is emitted when new address is accepted as admin. + // Grantor indicates who authorizes the add admin operation. + event AddAdminEvent(address addr, address grantor, string description); + + // RemoveAdminEvent is emitted when an admin is removed. + // Grantor indicates who authorizes the remove admin operation. + event RemoveAdminEvent(address addr, address grantor, string reason); + + /* + Public Functions + */ + constructor(address[] _adminlist) public { + // regard contract creator as a default admin. + admins[msg.sender] = 1; + adminList.push(msg.sender); + for (uint i = 0; i < _adminlist.length; i++) { + admins[_adminlist[i]] = 1; + adminList.push(_adminlist[i]); + } + } + + /** + * @dev Get latest stable checkpoint information. + * @return section index + * @return checkpoint hash + */ + function GetLatestCheckpoint() + view + public + returns(uint, bytes32) { + bytes32 hash = GetCheckpoint(latest); + return (latest, hash); + } + + /** + * @dev Get a stable checkpoint information with specified section index. + * @param _sectionIndex section index + * @return checkpoint hash + */ + function GetCheckpoint(uint _sectionIndex) + view + public + returns(bytes32) + { + return checkpoints[_sectionIndex]; + } + + /** + * @dev Set stable checkpoint information. + * Checkpoint represents a set of post-processed trie roots (CHT and BloomTrie) + * associated with the appropriate section head hash. + * + * It is used to start light syncing from this checkpoint + * and avoid downloading the entire header chain while still being able to securely + * access old headers/logs. + * + * Note we trust the given information here provided by foundation, + * need a trust less version for future. + * @param _sectionIndex section index + * @param _hash checkpoint hash calculated in the client side + * @return indicator whether set checkpoint successfully + */ + function SetCheckpoint( + uint _sectionIndex, + bytes32 _hash + ) + OnlyAuthorized + public + returns(bool) + { + // Ensure the checkpoint information provided is strictly continuous with previous one. + // But the latest checkpoint modification is allowed. + if (_sectionIndex != latest && _sectionIndex != latest + 1 && latest != 0) { + return false; + } + // Ensure the checkpoint is stable enough to be registered. + if (block.number < (_sectionIndex+1)*sectionSize+processConfirmations) { + return false; + } + // Ensure the modification for registered checkpoint within the allowed time range + if (latest != 0 && _sectionIndex == latest && block.number >= (_sectionIndex+1)*sectionSize+confirmations) { + return false; + } + + checkpoints[_sectionIndex] = _hash; + latest = _sectionIndex; + + emit NewCheckpointEvent(_sectionIndex, msg.sender, _hash); + } + + /** + * @dev Add a new address to admin list + * @param _addr specified new admin address. + * @return indicator whether add new admin successfully + */ + function AddAdmin(address _addr, string _description) + OnlyAuthorized + public + returns(bool) + { + // Ensure the specified address is not admin yet. + if (admins[_addr] > 0) { + return false; + } + admins[_addr] = 1; + adminList.push(_addr); + + emit AddAdminEvent(_addr, msg.sender, _description); + return true; + } + + /** + * @dev Remove a admin from the list + * @param _addr specified admin address to remove. + * @return indicator whether remove admin successfully + */ + function RemoveAdmin(address _addr, string _reason) + OnlyAuthorized + public + returns(bool) + { + // Ensure the specified address is admin. + if (admins[_addr] == 0) { + return false; + } + delete admins[_addr]; + for (uint i = 0; i < adminList.length; i++) { + if (adminList[i] == _addr) { + // Not leave a gap + for (uint idx = i; idx < adminList.length-1; idx++){ + adminList[idx] = adminList[idx+1]; + } + delete adminList[adminList.length-1]; + adminList.length -= 1; + break; + } + } + + emit RemoveAdminEvent(_addr, msg.sender, _reason); + return true; + } + + + /** + * @dev Get all admin addresses + * @return address list + */ + function GetAllAdmin() + public + view + returns(address[]) + { + address[] memory ret = new address[](adminList.length); + for (uint i = 0; i < adminList.length; i++) { + ret[i] = adminList[i]; + } + return ret; + } + + /* + Fields + */ + + // A map of admin users who have the permission to update CHT and bloom Trie root + mapping(address => uint) admins; + + // A list of admin users so that we can obtain all admin users. + address[] adminList; + + // Registered checkpoint information + mapping(uint => bytes32) checkpoints; + + // Latest stored section id + // Note all registered checkpoint information should continuous with previous one. + uint latest; + + // The frequency for creating a checkpoint + uint constant sectionSize = 32768; + + // The number of confirmations needed before a checkpoint can be registered. + // We have to make sure the checkpoint registered will not be invalid due to + // chain reorg. + uint constant processConfirmations = 256; + + // The number of confirmations when a registered checkpoint can not be modified. + uint constant confirmations = 8192; +} + diff --git a/contracts/registrar/registrar.go b/contracts/registrar/registrar.go new file mode 100644 index 0000000000..a7d8ce81c3 --- /dev/null +++ b/contracts/registrar/registrar.go @@ -0,0 +1,73 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package registrar + +import ( + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/contracts/registrar/contract" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/params" +) + +var ( + // registrar contract address for mainnet and testnet. + RegistrarAddr = map[common.Hash]common.Address{ + // params.MainnetGenesisHash: common.HexToAddress(""), + // params.TestnetGenesisHash: common.HexToAddress(""), + params.RinkebyGenesisHash: common.HexToAddress("0xc72f57e41e2498ad3dab92f665b0f21e2c4f4b79"), + } +) + +var errEventNotFound = errors.New("contract event not found") + +type Registrar struct { + contract *contract.Contract +} + +// NewRegistrar binds checkpoint contract and returns a registrar instance. +func NewRegistrar(contractAddr common.Address, backend bind.ContractBackend) (*Registrar, error) { + contract, err := contract.NewContract(contractAddr, backend) + if err != nil { + return nil, err + } + + return &Registrar{ + contract: contract, + }, nil +} + +// WatchNewCheckpointEvent watches new fired NewCheckpointEvent and delivers all matching events by result channel. +func (registrar *Registrar) WatchNewCheckpointEvent(sink chan<- *contract.ContractNewCheckpointEvent) (event.Subscription, error) { + return registrar.contract.WatchNewCheckpointEvent(nil, sink, nil) +} + +// FilterNewCheckpointEvent filters out NewCheckpointEvent for specific section number. +func (registrar *Registrar) FilterNewCheckpointEvent(head, section, sectionSize, processConfirm uint64) (*contract.ContractNewCheckpointEventIterator, error) { + start := (section+1)*sectionSize + processConfirm + if head < start { + return nil, errEventNotFound + } + opt := &bind.FilterOpts{ + Start: start, + End: &head, + } + return registrar.contract.FilterNewCheckpointEvent(opt, []*big.Int{big.NewInt(int64(section))}) +} diff --git a/contracts/registrar/registrar_test.go b/contracts/registrar/registrar_test.go new file mode 100644 index 0000000000..46f6ff1948 --- /dev/null +++ b/contracts/registrar/registrar_test.go @@ -0,0 +1,297 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package registrar + +import ( + "errors" + "math/big" + "reflect" + "testing" + "time" + + "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/contracts/registrar/contract" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/light" +) + +var ( + key, _ = crypto.GenerateKey() + addr = crypto.PubkeyToAddress(key.PublicKey) + emptyHash = [32]byte{} + + checkpointHash0 = crypto.Keccak256Hash(common.FromHex("dead0"), common.FromHex("beef0"), common.FromHex("deadbeef0")) + checkpointHash1 = crypto.Keccak256Hash(common.FromHex("dead1"), common.FromHex("beef1"), common.FromHex("deadbeef1")) +) + +// validateOperation executes the operation, watches and delivers all events fired by the backend and ensures the +// correctness by assert function. +func validateOperation(t *testing.T, c *contract.Contract, backend *backends.SimulatedBackend, operation func(), + assert func(<-chan *contract.ContractNewCheckpointEvent, <-chan *contract.ContractAddAdminEvent, <-chan *contract.ContractRemoveAdminEvent) error, opName string) { + // Watch all events and deliver them to assert function + var ( + sink1 = make(chan *contract.ContractNewCheckpointEvent) + sink2 = make(chan *contract.ContractAddAdminEvent) + sink3 = make(chan *contract.ContractRemoveAdminEvent) + ) + sub1, _ := c.WatchNewCheckpointEvent(nil, sink1, nil) + sub2, _ := c.WatchAddAdminEvent(nil, sink2) + sub3, _ := c.WatchRemoveAdminEvent(nil, sink3) + defer func() { + // Close all subscribers + sub1.Unsubscribe() + sub2.Unsubscribe() + sub3.Unsubscribe() + }() + operation() + + // flush pending block + backend.Commit() + if err := assert(sink1, sink2, sink3); err != nil { + t.Errorf("operation {%s} failed, err %s", opName, err) + } +} + +// validateEvents checks that the correct number of contract events +// fired by contract backend. +func validateEvents(target int, sink interface{}) bool { + chanval := reflect.ValueOf(sink) + chantyp := chanval.Type() + if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.RecvDir == 0 { + return false + } + count := 0 + timeout := time.After(1 * time.Second) + cases := []reflect.SelectCase{{Chan: chanval, Dir: reflect.SelectRecv}, {Chan: reflect.ValueOf(timeout), Dir: reflect.SelectRecv}} + for { + chose, _, _ := reflect.Select(cases) + if chose == 1 { + // Not enough event received + return false + } + count += 1 + if count == target { + break + } + } + done := time.After(50 * time.Millisecond) + cases = cases[:1] + cases = append(cases, reflect.SelectCase{Chan: reflect.ValueOf(done), Dir: reflect.SelectRecv}) + chose, _, _ := reflect.Select(cases) + // If chose equal 0, it means receiving redundant events. + return chose == 1 +} + +// Tests contract administrator managements. +func TestAdminManagement(t *testing.T) { + var ( + adminCandidate = common.HexToAddress("0xdead") + adminCandidate2 = common.HexToAddress("0xbeef") + ) + + // Deploy registrar contract + transactOpts := bind.NewKeyedTransactor(key) + contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}) + _, _, c, err := contract.DeployContract(transactOpts, contractBackend, nil) + if err != nil { + t.Error("deploy registrar contract failed", err) + } + contractBackend.Commit() + + // Test AddAdmin function + validateOperation(t, c, contractBackend, func() { + for _, a := range []common.Address{addr, adminCandidate, adminCandidate2} { + c.AddAdmin(transactOpts, a, "") + } + }, func(sink1 <-chan *contract.ContractNewCheckpointEvent, sink2 <-chan *contract.ContractAddAdminEvent, sink3 <-chan *contract.ContractRemoveAdminEvent) error { + adminList, err := c.GetAllAdmin(nil) + if err != nil { + return errors.New("get admin list failed") + } + if !reflect.DeepEqual(adminList, []common.Address{addr, adminCandidate, adminCandidate2}) { + return errors.New("add admin failed") + } + if !validateEvents(2, sink2) { + return errors.New("receive incorrect number of events") + } + return nil + }, "add admin") + + // Test Remove admin function + validateOperation(t, c, contractBackend, func() { + c.RemoveAdmin(transactOpts, adminCandidate, "") + }, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error { + adminList, err := c.GetAllAdmin(nil) + if err != nil { + return errors.New("get admin list failed") + } + if !reflect.DeepEqual(adminList, []common.Address{addr, adminCandidate2}) { + return errors.New("remove admin failed") + } + if !validateEvents(1, events3) { + return errors.New("receive incorrect number of events") + } + return nil + }, "remove admin at middle") + + // Test RemoveAdmin function (remove at the head) + validateOperation(t, c, contractBackend, func() { + c.RemoveAdmin(transactOpts, addr, "") + }, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error { + adminList, err := c.GetAllAdmin(nil) + if err != nil { + return errors.New("get admin list failed") + } + if !reflect.DeepEqual(adminList, []common.Address{adminCandidate2}) { + return errors.New("remove admin failed") + } + if !validateEvents(1, events3) { + return errors.New("receive incorrect number of events") + } + return nil + }, "remove admin at head") + + // Test unauthorized operation + validateOperation(t, c, contractBackend, func() { + c.AddAdmin(transactOpts, adminCandidate, "") + }, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error { + adminList, err := c.GetAllAdmin(nil) + if err != nil { + return errors.New("get admin list failed") + } + if !reflect.DeepEqual(adminList, []common.Address{adminCandidate2}) { + return errors.New("unauthorized operation should be banned") + } + return nil + }, "unauthorized operation") +} + +// Tests checkpoint managements. +func TestCheckpointRegister(t *testing.T) { + // Deploy registrar contract + transactOpts := bind.NewKeyedTransactor(key) + contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}) + _, _, c, err := contract.DeployContract(transactOpts, contractBackend, []common.Address{addr}) + if err != nil { + t.Error("deploy registrar contract failed", err) + } + contractBackend.Commit() + + // Register unstable checkpoint + validateOperation(t, c, contractBackend, func() { + c.SetCheckpoint(transactOpts, big.NewInt(0), checkpointHash0) + }, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error { + hash, err := c.GetCheckpoint(nil, big.NewInt(0)) + if err != nil { + return errors.New("get checkpoint failed") + } + if hash != emptyHash { + return errors.New("unstable checkpoint should be banned") + } + return nil + }, "register unstable checkpoint") + + contractBackend.ShiftBlocks(light.CheckpointFrequency + light.CheckpointProcessConfirmations) + + // Register by unauthorized user + validateOperation(t, c, contractBackend, func() { + user2, _ := crypto.GenerateKey() + unauthorized := bind.NewKeyedTransactor(user2) + c.SetCheckpoint(unauthorized, big.NewInt(0), checkpointHash0) + }, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error { + hash, err := c.GetCheckpoint(nil, big.NewInt(0)) + if err != nil { + return errors.New("get checkpoint failed") + } + if hash != emptyHash { + return errors.New("checkpoint from unauthorized user should be banned") + } + return nil + }, "register by unauthorized user") + + // Register a stable checkpoint + validateOperation(t, c, contractBackend, func() { + c.SetCheckpoint(transactOpts, big.NewInt(0), checkpointHash0) + }, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error { + hash, err := c.GetCheckpoint(nil, big.NewInt(0)) + if err != nil { + return errors.New("get checkpoint failed") + } + if hash != checkpointHash0 { + return errors.New("register stable checkpoint failed") + } + if !validateEvents(1, events) { + return errors.New("receive incorrect number of events") + } + return nil + }, "register stable checkpoint") + + newHash := crypto.Keccak256Hash(common.FromHex("dead00"), common.FromHex("beef00"), common.FromHex("deadbeef00")) + // Modify the latest checkpoint + validateOperation(t, c, contractBackend, func() { + c.SetCheckpoint(transactOpts, big.NewInt(0), newHash) + }, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error { + hash, err := c.GetCheckpoint(nil, big.NewInt(0)) + if err != nil { + return errors.New("get checkpoint failed") + } + if hash != newHash { + return errors.New("register stable checkpoint failed") + } + if !validateEvents(1, events) { + return errors.New("receive incorrect number of events") + } + return nil + }, "modify latest checkpoint") + + contractBackend.ShiftBlocks(light.CheckpointFrequency) + // Register checkpoint 1 + validateOperation(t, c, contractBackend, func() { + c.SetCheckpoint(transactOpts, big.NewInt(1), checkpointHash1) + }, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error { + hash, err := c.GetCheckpoint(nil, big.NewInt(1)) + if err != nil { + return errors.New("get checkpoint failed") + } + if hash != checkpointHash1 { + return errors.New("register stable checkpoint failed") + } + if !validateEvents(1, events) { + return errors.New("receive incorrect number of events") + } + return nil + }, "register stable checkpoint 1") + + contractBackend.ShiftBlocks(light.CheckpointConfirmations) + newHash1 := crypto.Keccak256Hash(common.FromHex("dead11"), common.FromHex("beef11"), common.FromHex("deadbeef11")) + // Modify the registered checkpoint after a very long time + validateOperation(t, c, contractBackend, func() { + c.SetCheckpoint(transactOpts, big.NewInt(1), newHash1) + }, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error { + hash, err := c.GetCheckpoint(nil, big.NewInt(1)) + if err != nil { + return errors.New("get checkpoint failed") + } + if hash != checkpointHash1 { + return errors.New("checkpoint modified out of allowed time range") + } + return nil + }, "modify checkpoint out of allowed time range") +} diff --git a/eth/backend.go b/eth/backend.go index 32946a0ab3..44028927e8 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -39,6 +39,7 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/ethapi" @@ -54,8 +55,10 @@ import ( type LesServer interface { Start(srvr *p2p.Server) Stop() + APIs() []rpc.API Protocols() []p2p.Protocol SetBloomBitsIndexer(bbIndexer *core.ChainIndexer) + SetClient(*ethclient.Client) } // Ethereum implements the Ethereum full node service. @@ -99,6 +102,14 @@ func (s *Ethereum) AddLesServer(ls LesServer) { ls.SetBloomBitsIndexer(s.bloomIndexer) } +// SetClient sets a rpc client which connecting to our local node. +func (s *Ethereum) SetClient(client *ethclient.Client) { + // Pass the rpc client to les server if it is enabled. + if s.lesServer != nil { + s.lesServer.SetClient(client) + } +} + // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { @@ -248,6 +259,11 @@ func (s *Ethereum) APIs() []rpc.API { // Append any APIs exposed explicitly by the consensus engine apis = append(apis, s.engine.APIs(s.BlockChain())...) + // Append any APIs exposed explicitly by the les server + if s.lesServer != nil { + apis = append(apis, s.lesServer.APIs()...) + } + // Append all the local APIs and return return append(apis, []rpc.API{ { diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 000e3728de..cc00236531 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -31,6 +31,7 @@ var Modules = map[string]string{ "shh": Shh_JS, "swarmfs": SWARMFS_JS, "txpool": TxPool_JS, + "les": LES_JS, } const Chequebook_JS = ` @@ -670,3 +671,24 @@ web3._extend({ ] }); ` + +const LES_JS = ` +web3._extend({ + property: 'les', + methods: + [ + new web3._extend.Method({ + name: 'getCheckpoint', + call: 'les_getCheckpoint', + params: 1 + }), + ], + properties: + [ + new web3._extend.Property({ + name: 'latestCheckpoint', + getter: 'les_latestCheckpoint' + }), + ] +}); +` diff --git a/les/api.go b/les/api.go new file mode 100644 index 0000000000..27d3e3975f --- /dev/null +++ b/les/api.go @@ -0,0 +1,75 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package les + +import ( + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var ( + errNoCheckpoint = errors.New("no local checkpoint provided") +) + +// PrivateLesServerAPI provides a private API to access the les server. +type PrivateLesServerAPI struct { + server *LesServer +} + +// NewPrivateLesServerAPI creates a new les server API. +func NewPrivateLesServerAPI(server *LesServer) *PrivateLesServerAPI { + return &PrivateLesServerAPI{ + server: server, + } +} + +// Checkpoint returns the latest local checkpoint package. +// +// The checkpoint package consists of 4 strings: +// result[0], hex encoded latest section index +// result[1], 32 bytes hex encoded latest section head hash +// result[2], 32 bytes hex encoded latest section canonical hash trie root hash +// result[3], 32 bytes hex encoded latest section bloom trie root hash +func (api *PrivateLesServerAPI) LatestCheckpoint() ([4]string, error) { + var res [4]string + sectionIdx, sectionHead, chtRoot, bloomTrieRoot := api.server.latestCheckpoint() + if sectionHead == (common.Hash{}) || chtRoot == (common.Hash{}) || bloomTrieRoot == (common.Hash{}) { + return res, errNoCheckpoint + } + res[0] = hexutil.Encode(big.NewInt(int64(sectionIdx)).Bytes()) + res[1], res[2], res[3] = sectionHead.Hex(), chtRoot.Hex(), bloomTrieRoot.Hex() + return res, nil +} + +// GetCheckpoint returns the specific local checkpoint package. +// +// The checkpoint package consists of 3 strings: +// result[0], 32 bytes hex encoded latest section head hash +// result[1], 32 bytes hex encoded latest section canonical hash trie root hash +// result[2], 32 bytes hex encoded latest section bloom trie root hash +func (api *PrivateLesServerAPI) GetCheckpoint(index uint64) ([3]string, error) { + var res [3]string + sectionHead, chtRoot, bloomTrieRoot := api.server.getCheckpoint(index) + if sectionHead == (common.Hash{}) || chtRoot == (common.Hash{}) || bloomTrieRoot == (common.Hash{}) { + return res, errNoCheckpoint + } + res[0], res[1], res[2] = sectionHead.Hex(), chtRoot.Hex(), bloomTrieRoot.Hex() + return res, nil +} diff --git a/les/costtable.go b/les/costtable.go new file mode 100644 index 0000000000..e651bec28f --- /dev/null +++ b/les/costtable.go @@ -0,0 +1,199 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package les + +import ( + "encoding/binary" + "math" + "sync" + + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rlp" +) + +type requestCosts struct { + baseCost, reqCost uint64 +} + +type requestCostTable map[uint64]*requestCosts + +type RequestCostList []struct { + MsgCode, BaseCost, ReqCost uint64 +} + +func (list RequestCostList) decode() requestCostTable { + table := make(requestCostTable) + for _, e := range list { + table[e.MsgCode] = &requestCosts{ + baseCost: e.BaseCost, + reqCost: e.ReqCost, + } + } + return table +} + +type linReg struct { + sumX, sumY, sumXX, sumXY float64 + cnt uint64 +} + +const linRegMaxCnt = 100000 + +func (l *linReg) add(x, y float64) { + if l.cnt >= linRegMaxCnt { + sub := float64(l.cnt+1-linRegMaxCnt) / linRegMaxCnt + l.sumX -= l.sumX * sub + l.sumY -= l.sumY * sub + l.sumXX -= l.sumXX * sub + l.sumXY -= l.sumXY * sub + l.cnt = linRegMaxCnt - 1 + } + l.cnt++ + l.sumX += x + l.sumY += y + l.sumXX += x * x + l.sumXY += x * y +} + +func (l *linReg) calc() (b, m float64) { + if l.cnt == 0 { + return 0, 0 + } + cnt := float64(l.cnt) + d := cnt*l.sumXX - l.sumX*l.sumX + if d < 0.001 { + return l.sumY / cnt, 0 + } + m = (cnt*l.sumXY - l.sumX*l.sumY) / d + b = (l.sumY / cnt) - (m * l.sumX / cnt) + return b, m +} + +func (l *linReg) toBytes() []byte { + var arr [40]byte + binary.BigEndian.PutUint64(arr[0:8], math.Float64bits(l.sumX)) + binary.BigEndian.PutUint64(arr[8:16], math.Float64bits(l.sumY)) + binary.BigEndian.PutUint64(arr[16:24], math.Float64bits(l.sumXX)) + binary.BigEndian.PutUint64(arr[24:32], math.Float64bits(l.sumXY)) + binary.BigEndian.PutUint64(arr[32:40], l.cnt) + return arr[:] +} + +func linRegFromBytes(data []byte) *linReg { + if len(data) != 40 { + return nil + } + l := &linReg{} + l.sumX = math.Float64frombits(binary.BigEndian.Uint64(data[0:8])) + l.sumY = math.Float64frombits(binary.BigEndian.Uint64(data[8:16])) + l.sumXX = math.Float64frombits(binary.BigEndian.Uint64(data[16:24])) + l.sumXY = math.Float64frombits(binary.BigEndian.Uint64(data[24:32])) + l.cnt = binary.BigEndian.Uint64(data[32:40]) + return l +} + +type requestCostStats struct { + lock sync.RWMutex + db ethdb.Database + stats map[uint64]*linReg +} + +type requestCostStatsRlp []struct { + MsgCode uint64 + Data []byte +} + +var rcStatsKey = []byte("_requestCostStats") + +func newCostStats(db ethdb.Database) *requestCostStats { + stats := make(map[uint64]*linReg) + for _, code := range reqList { + stats[code] = &linReg{cnt: 100} + } + + if db != nil { + data, err := db.Get(rcStatsKey) + var statsRlp requestCostStatsRlp + if err == nil { + err = rlp.DecodeBytes(data, &statsRlp) + } + if err == nil { + for _, r := range statsRlp { + if stats[r.MsgCode] != nil { + if l := linRegFromBytes(r.Data); l != nil { + stats[r.MsgCode] = l + } + } + } + } + } + + return &requestCostStats{ + db: db, + stats: stats, + } +} + +func (s *requestCostStats) store() { + s.lock.Lock() + defer s.lock.Unlock() + + statsRlp := make(requestCostStatsRlp, len(reqList)) + for i, code := range reqList { + statsRlp[i].MsgCode = code + statsRlp[i].Data = s.stats[code].toBytes() + } + + if data, err := rlp.EncodeToBytes(statsRlp); err == nil { + s.db.Put(rcStatsKey, data) + } +} + +func (s *requestCostStats) getCurrentList() RequestCostList { + s.lock.Lock() + defer s.lock.Unlock() + + list := make(RequestCostList, len(reqList)) + //fmt.Println("RequestCostList") + for idx, code := range reqList { + b, m := s.stats[code].calc() + //fmt.Println(code, s.stats[code].cnt, b/1000000, m/1000000) + if m < 0 { + b += m + m = 0 + } + if b < 0 { + b = 0 + } + + list[idx].MsgCode = code + list[idx].BaseCost = uint64(b * 2) + list[idx].ReqCost = uint64(m * 2) + } + return list +} + +func (s *requestCostStats) update(msgCode, reqCnt, cost uint64) { + s.lock.Lock() + defer s.lock.Unlock() + + c, ok := s.stats[msgCode] + if !ok || reqCnt == 0 { + return + } + c.add(float64(reqCnt), float64(cost)) +} diff --git a/les/server.go b/les/server.go index fca6124c9c..39929c6685 100644 --- a/les/server.go +++ b/les/server.go @@ -19,26 +19,35 @@ package les import ( "crypto/ecdsa" - "encoding/binary" - "math" + "errors" "sync" + "sync/atomic" + "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/contracts/registrar" + "github.com/ethereum/go-ethereum/contracts/registrar/contract" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discv5" - "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" ) +// chainHeadChanSize is the size of channel listening to ChainHeadEvent. +const SubscribeChainHeadEvent = 10 + type LesServer struct { config *eth.Config + backend *eth.EthAPIBackend + chaindb ethdb.Database protocolManager *ProtocolManager fcManager *flowcontrol.ClientManager // nil if our node is client only fcCostStats *requestCostStats @@ -47,28 +56,38 @@ type LesServer struct { privateKey *ecdsa.PrivateKey quitSync chan struct{} - chtIndexer, bloomTrieIndexer *core.ChainIndexer + // Checkpoint contract relative fields + genesis common.Hash // Genesis block hash for contract address detection + registrar *registrar.Registrar // Handler for checkpoint contract, initialized after server is started. + watching int32 // Indicator whether the checkpoint contract is being watched + + // Indexers + chtIndexer *core.ChainIndexer // Indexers for creating cht root for each block section + bloomTrieIndexer *core.ChainIndexer // Indexers for creating bloom trie root for each block section } -func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) { +func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) { quitSync := make(chan struct{}) - pm, err := NewProtocolManager(eth.BlockChain().Config(), false, ServerProtocolVersions, config.NetworkId, eth.EventMux(), eth.Engine(), newPeerSet(), eth.BlockChain(), eth.TxPool(), eth.ChainDb(), nil, nil, nil, quitSync, new(sync.WaitGroup)) + pm, err := NewProtocolManager(e.BlockChain().Config(), false, ServerProtocolVersions, config.NetworkId, e.EventMux(), e.Engine(), newPeerSet(), e.BlockChain(), e.TxPool(), e.ChainDb(), nil, nil, nil, quitSync, new(sync.WaitGroup)) if err != nil { return nil, err } lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions)) for i, pv := range AdvertiseProtocolVersions { - lesTopics[i] = lesTopic(eth.BlockChain().Genesis().Hash(), pv) + lesTopics[i] = lesTopic(e.BlockChain().Genesis().Hash(), pv) } srv := &LesServer{ config: config, + backend: e.APIBackend, + chaindb: e.ChainDb(), protocolManager: pm, quitSync: quitSync, lesTopics: lesTopics, - chtIndexer: light.NewChtIndexer(eth.ChainDb(), false), - bloomTrieIndexer: light.NewBloomTrieIndexer(eth.ChainDb(), false), + chtIndexer: light.NewChtIndexer(e.ChainDb(), false), + bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), false), + genesis: e.BlockChain().Genesis().Hash(), } logger := log.New() @@ -80,18 +99,18 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) { // convert last LES/2 section index back to LES/1 index for chtIndexer.SectionHead chtLastSectionV1 := (chtLastSection+1)*(light.CHTFrequencyClient/light.CHTFrequencyServer) - 1 chtSectionHead := srv.chtIndexer.SectionHead(chtLastSectionV1) - chtRoot := light.GetChtV2Root(pm.chainDb, chtLastSection, chtSectionHead) + chtRoot := light.GetChtV2Root(srv.chaindb, chtLastSection, chtSectionHead) logger.Info("Loaded CHT", "section", chtLastSection, "head", chtSectionHead, "root", chtRoot) } bloomTrieSectionCount, _, _ := srv.bloomTrieIndexer.Sections() if bloomTrieSectionCount != 0 { bloomTrieLastSection := bloomTrieSectionCount - 1 bloomTrieSectionHead := srv.bloomTrieIndexer.SectionHead(bloomTrieLastSection) - bloomTrieRoot := light.GetBloomTrieRoot(pm.chainDb, bloomTrieLastSection, bloomTrieSectionHead) + bloomTrieRoot := light.GetBloomTrieRoot(srv.chaindb, bloomTrieLastSection, bloomTrieSectionHead) logger.Info("Loaded bloom trie", "section", bloomTrieLastSection, "head", bloomTrieSectionHead, "root", bloomTrieRoot) } - srv.chtIndexer.Start(eth.BlockChain()) + srv.chtIndexer.Start(e.BlockChain()) pm.server = srv srv.defParams = &flowcontrol.ServerParams{ @@ -99,7 +118,7 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) { MinRecharge: 50000, } srv.fcManager = flowcontrol.NewClientManager(uint64(config.LightServ), 10, 1000000000) - srv.fcCostStats = newCostStats(eth.ChainDb()) + srv.fcCostStats = newCostStats(e.ChainDb()) return srv, nil } @@ -130,6 +149,26 @@ func (s *LesServer) SetBloomBitsIndexer(bloomIndexer *core.ChainIndexer) { bloomIndexer.AddChildIndexer(s.bloomTrieIndexer) } +// SetClient sets the rpc client and starts watching checkpoint contract if it is not yet watched. +func (s *LesServer) SetClient(client *ethclient.Client) { + addr, ok := registrar.RegistrarAddr[s.genesis] + if !ok { + log.Info("The registrar contract is not deployed") + return + } + registrar, err := registrar.NewRegistrar(addr, client) + if err != nil { + log.Info("Bind registrar contract failed", "err", err) + return + } + if !atomic.CompareAndSwapInt32(&s.watching, 0, 1) { + log.Info("Already bound and listening to registrar contract") + return + } + s.registrar = registrar + go s.checkpointLoop(s.recoverCheckpoint()) +} + // Stop stops the LES service func (s *LesServer) Stop() { s.chtIndexer.Close() @@ -140,179 +179,187 @@ func (s *LesServer) Stop() { <-s.protocolManager.noMorePeers }() s.protocolManager.Stop() + atomic.StoreInt32(&s.watching, 0) } -type requestCosts struct { - baseCost, reqCost uint64 -} - -type requestCostTable map[uint64]*requestCosts - -type RequestCostList []struct { - MsgCode, BaseCost, ReqCost uint64 -} - -func (list RequestCostList) decode() requestCostTable { - table := make(requestCostTable) - for _, e := range list { - table[e.MsgCode] = &requestCosts{ - baseCost: e.BaseCost, - reqCost: e.ReqCost, - } +// APIs implements LesServer, returns all API service provided by les server. +func (s *LesServer) APIs() []rpc.API { + return []rpc.API{ + { + Namespace: "les", + Version: "1.0", + Service: NewPrivateLesServerAPI(s), + Public: false, + }, } - return table } -type linReg struct { - sumX, sumY, sumXX, sumXY float64 - cnt uint64 -} - -const linRegMaxCnt = 100000 - -func (l *linReg) add(x, y float64) { - if l.cnt >= linRegMaxCnt { - sub := float64(l.cnt+1-linRegMaxCnt) / linRegMaxCnt - l.sumX -= l.sumX * sub - l.sumY -= l.sumY * sub - l.sumXX -= l.sumXX * sub - l.sumXY -= l.sumXY * sub - l.cnt = linRegMaxCnt - 1 +// latestCheckpoint finds the common stored section index and returns a set of +// post-processed trie roots (CHT and BloomTrie) associated with +// the appropriate section index and head hash as a local checkpoint package. +// +// Note for cht, the section size in LES1 is 4K, so indexer still uses LES/1 +// 4k section size for backwards server compatibility. For bloomTrie, the size +// of the section used for indexer is 32K. +func (s *LesServer) latestCheckpoint() (uint64, common.Hash, common.Hash, common.Hash) { + chtCount, _, _ := s.chtIndexer.Sections() + bloomTrieCount, _, _ := s.bloomTrieIndexer.Sections() + count := chtCount / (light.CHTFrequencyClient / light.CHTFrequencyServer) + // Cap the section index if the two sections are not consistent. + if count > bloomTrieCount { + count = bloomTrieCount } - l.cnt++ - l.sumX += x - l.sumY += y - l.sumXX += x * x - l.sumXY += x * y -} - -func (l *linReg) calc() (b, m float64) { - if l.cnt == 0 { - return 0, 0 + if count == 0 { + // No checkpoint information can be provided. + return 0, common.Hash{}, common.Hash{}, common.Hash{} } - cnt := float64(l.cnt) - d := cnt*l.sumXX - l.sumX*l.sumX - if d < 0.001 { - return l.sumY / cnt, 0 + sectionHead, chtRoot, bloomTrieRoot := s.getCheckpoint(count - 1) + return count - 1, sectionHead, chtRoot, bloomTrieRoot +} + +// getCheckpoint returns a set of post-processed trie roots (CHT and BloomTrie) +// associated with the appropriate head hash by specific section index. +// +// The returned checkpoint is only the checkpoint generated by the local indexers, +// not the stable checkpoint registered in the registrar contract. +func (s *LesServer) getCheckpoint(index uint64) (common.Hash, common.Hash, common.Hash) { + // convert last LES/2 section index back to LES/1 index for chtIndexer.SectionHead + latest := (index+1)*(light.CHTFrequencyClient/light.CHTFrequencyServer) - 1 + + sectionHead := s.chtIndexer.SectionHead(latest) + chtRoot := light.GetChtRoot(s.protocolManager.chainDb, latest, sectionHead) + bloomTrieRoot := light.GetBloomTrieRoot(s.protocolManager.chainDb, index, sectionHead) + return sectionHead, chtRoot, bloomTrieRoot +} + +// checkpointLoop starts a standalone goroutine to watch new checkpoint events and updates local's stable checkpoint. +func (s *LesServer) checkpointLoop(checkpoint *light.TrustedCheckpoint) (err error) { + var ( + eventCh = make(chan *contract.ContractNewCheckpointEvent) + headCh = make(chan core.ChainHeadEvent, SubscribeChainHeadEvent) + announcement = make(map[uint64]common.Hash) + ) + eventSub, err := s.registrar.WatchNewCheckpointEvent(eventCh) + if err != nil { + return err } - m = (cnt*l.sumXY - l.sumX*l.sumY) / d - b = (l.sumY / cnt) - (m * l.sumX / cnt) - return b, m -} - -func (l *linReg) toBytes() []byte { - var arr [40]byte - binary.BigEndian.PutUint64(arr[0:8], math.Float64bits(l.sumX)) - binary.BigEndian.PutUint64(arr[8:16], math.Float64bits(l.sumY)) - binary.BigEndian.PutUint64(arr[16:24], math.Float64bits(l.sumXX)) - binary.BigEndian.PutUint64(arr[24:32], math.Float64bits(l.sumXY)) - binary.BigEndian.PutUint64(arr[32:40], l.cnt) - return arr[:] -} - -func linRegFromBytes(data []byte) *linReg { - if len(data) != 40 { - return nil - } - l := &linReg{} - l.sumX = math.Float64frombits(binary.BigEndian.Uint64(data[0:8])) - l.sumY = math.Float64frombits(binary.BigEndian.Uint64(data[8:16])) - l.sumXX = math.Float64frombits(binary.BigEndian.Uint64(data[16:24])) - l.sumXY = math.Float64frombits(binary.BigEndian.Uint64(data[24:32])) - l.cnt = binary.BigEndian.Uint64(data[32:40]) - return l -} - -type requestCostStats struct { - lock sync.RWMutex - db ethdb.Database - stats map[uint64]*linReg -} - -type requestCostStatsRlp []struct { - MsgCode uint64 - Data []byte -} - -var rcStatsKey = []byte("_requestCostStats") - -func newCostStats(db ethdb.Database) *requestCostStats { - stats := make(map[uint64]*linReg) - for _, code := range reqList { - stats[code] = &linReg{cnt: 100} + headSub := s.backend.SubscribeChainHeadEvent(headCh) + if headSub == nil { + eventSub.Unsubscribe() + return errors.New("subscribe head event failed") } - if db != nil { - data, err := db.Get(rcStatsKey) - var statsRlp requestCostStatsRlp - if err == nil { - err = rlp.DecodeBytes(data, &statsRlp) - } - if err == nil { - for _, r := range statsRlp { - if stats[r.MsgCode] != nil { - if l := linRegFromBytes(r.Data); l != nil { - stats[r.MsgCode] = l - } + ticker := time.NewTicker(5 * time.Minute) + defer func() { + eventSub.Unsubscribe() + headSub.Unsubscribe() + ticker.Stop() + }() + + for { + select { + case event := <-eventCh: + if event == nil { + // This should never happen. + log.Info("Ignore empty checkpoint event") + continue + } + // Note several events have same index may be received because of chain reorg and + // the modification of the latest checkpoint. + if checkpoint == nil || event.Index.Uint64() > checkpoint.SectionIdx { + log.Info("Receive new checkpoint event", "section", event.Index, "hash", common.Hash(event.CheckpointHash).Hex(), + "grantor", event.Grantor.Hex()) + announcement[event.Index.Uint64()] = common.Hash(event.CheckpointHash) + } + case head := <-headCh: + number := head.Block.NumberU64() + if number < light.CheckpointConfirmations+light.CheckpointFrequency { + continue + } + if checkpoint == nil { + checkpoint = s.recoverCheckpoint() + } + idx := (number-light.CheckpointConfirmations)/light.CheckpointFrequency - 1 + if checkpoint == nil || idx > checkpoint.SectionIdx { + hash, ok := announcement[idx] + if !ok { + continue + } + sectionHead := s.bloomTrieIndexer.SectionHead(idx) + c := &light.TrustedCheckpoint{ + SectionIdx: idx, + SectionHead: sectionHead, + ChtRoot: light.GetChtV2Root(s.chaindb, idx, sectionHead), + BloomTrieRoot: light.GetBloomTrieRoot(s.chaindb, idx, sectionHead), + } + if c.HashEqual(common.Hash(hash)) { + light.WriteTrustedCheckpoint(s.chaindb, c) + checkpoint = c + delete(announcement, idx) + log.Info("Update stable checkpoint", "section", checkpoint.SectionIdx, "hash", checkpoint.Hash().Hex()) } } + case <-ticker.C: + // Evict useless announcement every 5 minutes. + for idx := range announcement { + if checkpoint != nil && checkpoint.SectionIdx >= idx { + delete(announcement, idx) + } + } + case <-s.quitSync: + // Les server is closed. + return } } - - return &requestCostStats{ - db: db, - stats: stats, - } } -func (s *requestCostStats) store() { - s.lock.Lock() - defer s.lock.Unlock() - - statsRlp := make(requestCostStatsRlp, len(reqList)) - for i, code := range reqList { - statsRlp[i].MsgCode = code - statsRlp[i].Data = s.stats[code].toBytes() +// recoveryCheckpoint filters checkpoint announcement events and recovers stable checkpoint. +func (s *LesServer) recoverCheckpoint() *light.TrustedCheckpoint { + var ( + sectionCnt, _, _ = s.bloomTrieIndexer.Sections() + stable = light.ReadTrustedCheckpoint(s.chaindb) + headHash = rawdb.ReadHeadHeaderHash(s.chaindb) + headNumber = rawdb.ReadHeaderNumber(s.chaindb, headHash) + ) + // Short circuit if there is no local checkpoint generated. + if headNumber == nil || sectionCnt == 0 { + return nil } - - if data, err := rlp.EncodeToBytes(statsRlp); err == nil { - s.db.Put(rcStatsKey, data) - } -} - -func (s *requestCostStats) getCurrentList() RequestCostList { - s.lock.Lock() - defer s.lock.Unlock() - - list := make(RequestCostList, len(reqList)) - //fmt.Println("RequestCostList") - for idx, code := range reqList { - b, m := s.stats[code].calc() - //fmt.Println(code, s.stats[code].cnt, b/1000000, m/1000000) - if m < 0 { - b += m - m = 0 + unstableIdx := sectionCnt - 1 + for stable == nil || stable.SectionIdx < unstableIdx { + if (unstableIdx+1)*light.CheckpointFrequency+light.CheckpointConfirmations <= *headNumber { + iter, err := s.registrar.FilterNewCheckpointEvent(*headNumber, unstableIdx, light.CheckpointFrequency, light.CheckpointProcessConfirmations) + if err != nil { + continue + } + for iter.Next() { + sectionHead := s.bloomTrieIndexer.SectionHead(unstableIdx) + checkpoint := &light.TrustedCheckpoint{ + SectionIdx: unstableIdx, + SectionHead: sectionHead, + ChtRoot: light.GetChtV2Root(s.chaindb, unstableIdx, sectionHead), + BloomTrieRoot: light.GetBloomTrieRoot(s.chaindb, unstableIdx, sectionHead), + } + if checkpoint.HashEqual(common.Hash(iter.Event.CheckpointHash)) { + light.WriteTrustedCheckpoint(s.chaindb, checkpoint) + iter.Close() + log.Info("Recover stable checkpoint", "index", checkpoint.SectionIdx, "hash", checkpoint.Hash().Hex()) + return checkpoint + } + } + iter.Close() } - if b < 0 { - b = 0 + if unstableIdx == 0 { + break } - - list[idx].MsgCode = code - list[idx].BaseCost = uint64(b * 2) - list[idx].ReqCost = uint64(m * 2) + unstableIdx -= 1 } - return list -} - -func (s *requestCostStats) update(msgCode, reqCnt, cost uint64) { - s.lock.Lock() - defer s.lock.Unlock() - - c, ok := s.stats[msgCode] - if !ok || reqCnt == 0 { - return + if stable == nil { + log.Info("No stable checkpoint") + } else { + log.Info("Recover stable checkpoint", "index", stable.SectionIdx, "hash", stable.Hash().Hex()) } - c.add(float64(reqCnt), float64(cost)) + return stable } func (pm *ProtocolManager) blockLoop() { diff --git a/light/checkpoint.go b/light/checkpoint.go new file mode 100644 index 0000000000..8933c84ac4 --- /dev/null +++ b/light/checkpoint.go @@ -0,0 +1,144 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package light + +import ( + "io" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +const ( + // CheckpointFrequency is the block frequency for creating checkpoint + CheckpointFrequency = 32768 + + // CheckpointProcessConfirmations is the number before a checkpoint is generated + CheckpointProcessConfirmations = 256 + + // CheckpointConfirmations is the number of confirmations before a checkpoint is stable + CheckpointConfirmations = 8192 +) + +// checkpointKey tracks the latest stable checkpoint. +var checkpointKey = []byte("Checkpoint") + +// TrustedCheckpoint represents a set of post-processed trie roots (CHT and BloomTrie) associated with +// the appropriate section index and head hash. +// +// It is used to start light syncing from this checkpoint and avoid downloading the entire header chain +// while still being able to securely access old headers/logs. +type TrustedCheckpoint struct { + Name string // Indicator which chain the checkpoint belongs to + SectionIdx uint64 // Section index + SectionHead common.Hash // Block Hash for the last block in the section + ChtRoot common.Hash // CHT(Canonical Hash Trie) root associated to the section + BloomTrieRoot common.Hash // Bloom Trie root associated to the section +} + +type trustCheckpointRLP struct { + SectionIdx uint64 + SectionHead common.Hash + ChtRoot common.Hash + BloomTrieRoot common.Hash +} + +// EncodeRLP implements rlp.Encoder, and flattens the necessary fields of a checkpoint +// into an RLP stream. +func (c *TrustedCheckpoint) EncodeRLP(w io.Writer) (err error) { + return rlp.Encode(w, &trustCheckpointRLP{c.SectionIdx, c.SectionHead, c.ChtRoot, c.BloomTrieRoot}) +} + +// DecodeRLP implements rlp.Decoder, and loads the necessary fields of a checkpoint +// from an RLP stream. +func (c *TrustedCheckpoint) DecodeRLP(s *rlp.Stream) error { + var dec trustCheckpointRLP + if err := s.Decode(&dec); err != nil { + return err + } + c.SectionIdx, c.SectionHead, c.ChtRoot, c.BloomTrieRoot = dec.SectionIdx, dec.SectionHead, dec.ChtRoot, dec.BloomTrieRoot + return nil +} + +// HashEqual returns an indicator comparing the itself hash with given one. +// A nil argument is equivalent to an empty slice. +func (c *TrustedCheckpoint) HashEqual(hash common.Hash) bool { + if c.SectionHead == (common.Hash{}) && c.ChtRoot == (common.Hash{}) && c.BloomTrieRoot == (common.Hash{}) { + return hash == common.Hash{} + } + return c.Hash() == hash +} + +// Hash returns the hash of checkpoint three key fields(sectionHead, chtRoot and bloomTrieRoot). +func (c *TrustedCheckpoint) Hash() common.Hash { + return crypto.Keccak256Hash(c.SectionHead.Bytes(), c.ChtRoot.Bytes(), c.BloomTrieRoot.Bytes()) +} + +var ( + // Hardcode checkpoint for mainnet and testnet(ropsten). Will be deleted eventually once checkpoint contract + // works. + mainnetCheckpoint = TrustedCheckpoint{ + Name: "mainnet", + SectionIdx: 179, + SectionHead: common.HexToHash("ae778e455492db1183e566fa0c67f954d256fdd08618f6d5a393b0e24576d0ea"), + ChtRoot: common.HexToHash("646b338f9ca74d936225338916be53710ec84020b89946004a8605f04c817f16"), + BloomTrieRoot: common.HexToHash("d0f978f5dbc86e5bf931d8dd5b2ecbebbda6dc78f8896af6a27b46a3ced0ac25"), + } + + ropstenCheckpoint = TrustedCheckpoint{ + Name: "ropsten", + SectionIdx: 107, + SectionHead: common.HexToHash("e1988f95399debf45b873e065e5cd61b416ef2e2e5deec5a6f87c3127086e1ce"), + ChtRoot: common.HexToHash("15cba18e4de0ab1e95e202625199ba30147aec8b0b70384b66ebea31ba6a18e0"), + BloomTrieRoot: common.HexToHash("e00fa6389b2e597d9df52172cd8e936879eed0fca4fa59db99e2c8ed682562f2"), + } +) + +// TrustedCheckpoints associates each known checkpoint with the genesis hash of the chain it belongs to. +var TrustedCheckpoints = map[common.Hash]TrustedCheckpoint{ + params.MainnetGenesisHash: mainnetCheckpoint, + params.TestnetGenesisHash: ropstenCheckpoint, +} + +// ReadTrustedCheckpoint retrieves the checkpoint from the database. +func ReadTrustedCheckpoint(db ethdb.Database) *TrustedCheckpoint { + data, err := db.Get(checkpointKey) + if err != nil { + return nil + } + c := new(TrustedCheckpoint) + if err := rlp.DecodeBytes(data, c); err != nil { + log.Error("Invalid checkpoint RLP", "err", err) + return nil + } + return c +} + +// WriteTrustedCheckpoint stores an RLP encoded checkpoint into the database. +func WriteTrustedCheckpoint(db ethdb.Putter, checkpoint *TrustedCheckpoint) { + data, err := rlp.EncodeToBytes(checkpoint) + if err != nil { + log.Crit("Failed to RLP encode checkpoint", err) + } + if err := db.Put(checkpointKey, data); err != nil { + log.Crit("Failed to store checkpoint", "err", err) + } +} diff --git a/light/checkpoint_test.go b/light/checkpoint_test.go new file mode 100644 index 0000000000..3b419c5ea8 --- /dev/null +++ b/light/checkpoint_test.go @@ -0,0 +1,55 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package light + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" +) + +var testCheckpoint = &TrustedCheckpoint{ + Name: "test", + SectionIdx: 100, + SectionHead: common.HexToHash("0xbeef"), + ChtRoot: common.HexToHash("0xdead"), + BloomTrieRoot: common.HexToHash("0xdeadbeef"), +} + +func TestRWCheckpoint(t *testing.T) { + mdb := ethdb.NewMemDatabase() + WriteTrustedCheckpoint(mdb, testCheckpoint) + if !assertCheckpointEqual(testCheckpoint, ReadTrustedCheckpoint(mdb)) { + t.Error("the checkpoint retrieved from database is different") + } +} + +func TestHashEqual(t *testing.T) { + if !testCheckpoint.HashEqual(common.HexToHash("0x6142a271d44a56107cd9de0be0a04211841593906b310f8c4d33be56b6e78959")) { + t.Error("checkpoint should hash equal to given one") + } + emptyCheckpoint := &TrustedCheckpoint{} + if !emptyCheckpoint.HashEqual(common.Hash{}) { + t.Error("empty checkpoint should equal to empty hash") + } +} + +func assertCheckpointEqual(ckp1, ckp2 *TrustedCheckpoint) bool { + return ckp1.SectionIdx == ckp2.SectionIdx && ckp1.SectionHead == ckp2.SectionHead && ckp1.ChtRoot == ckp2.ChtRoot && + ckp1.BloomTrieRoot == ckp2.BloomTrieRoot +} diff --git a/light/lightchain.go b/light/lightchain.go index 30b9bd89a6..c8ded16a22 100644 --- a/light/lightchain.go +++ b/light/lightchain.go @@ -98,7 +98,7 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus. if bc.genesisBlock == nil { return nil, core.ErrNoGenesis } - if cp, ok := trustedCheckpoints[bc.genesisBlock.Hash()]; ok { + if cp, ok := TrustedCheckpoints[bc.genesisBlock.Hash()]; ok { bc.addTrustedCheckpoint(cp) } if err := bc.loadLastState(); err != nil { @@ -116,19 +116,19 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus. } // addTrustedCheckpoint adds a trusted checkpoint to the blockchain -func (self *LightChain) addTrustedCheckpoint(cp trustedCheckpoint) { +func (self *LightChain) addTrustedCheckpoint(cp TrustedCheckpoint) { if self.odr.ChtIndexer() != nil { - StoreChtRoot(self.chainDb, cp.sectionIdx, cp.sectionHead, cp.chtRoot) - self.odr.ChtIndexer().AddKnownSectionHead(cp.sectionIdx, cp.sectionHead) + StoreChtRoot(self.chainDb, cp.SectionIdx, cp.SectionHead, cp.ChtRoot) + self.odr.ChtIndexer().AddKnownSectionHead(cp.SectionIdx, cp.SectionHead) } if self.odr.BloomTrieIndexer() != nil { - StoreBloomTrieRoot(self.chainDb, cp.sectionIdx, cp.sectionHead, cp.bloomTrieRoot) - self.odr.BloomTrieIndexer().AddKnownSectionHead(cp.sectionIdx, cp.sectionHead) + StoreBloomTrieRoot(self.chainDb, cp.SectionIdx, cp.SectionHead, cp.BloomTrieRoot) + self.odr.BloomTrieIndexer().AddKnownSectionHead(cp.SectionIdx, cp.SectionHead) } if self.odr.BloomIndexer() != nil { - self.odr.BloomIndexer().AddKnownSectionHead(cp.sectionIdx, cp.sectionHead) + self.odr.BloomIndexer().AddKnownSectionHead(cp.SectionIdx, cp.SectionHead) } - log.Info("Added trusted checkpoint", "chain", cp.name, "block", (cp.sectionIdx+1)*CHTFrequencyClient-1, "hash", cp.sectionHead) + log.Info("Added trusted checkpoint", "chain", cp.Name, "block", (cp.SectionIdx+1)*CHTFrequencyClient-1, "hash", cp.SectionHead) } func (self *LightChain) getProcInterrupt() bool { diff --git a/light/odr.go b/light/odr.go index 8f1e50b817..fe22c45c34 100644 --- a/light/odr.go +++ b/light/odr.go @@ -164,7 +164,7 @@ func (req *BloomRequest) StoreResult(db ethdb.Database) { for i, sectionIdx := range req.SectionIdxList { sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*BloomTrieFrequency-1) // if we don't have the canonical hash stored for this section head number, we'll still store it under - // a key with a zero sectionHead. GetBloomBits will look there too if we still don't have the canonical + // a key with a zero SectionHead. GetBloomBits will look there too if we still don't have the canonical // hash. In the unlikely case we've retrieved the section head hash since then, we'll just retrieve the // bit vector again from the network. rawdb.WriteBloomBits(db, req.BitIdx, sectionIdx, sectionHead, req.BloomBits[i]) diff --git a/light/odr_util.go b/light/odr_util.go index 620af63835..c909a93bfa 100644 --- a/light/odr_util.go +++ b/light/odr_util.go @@ -203,7 +203,7 @@ func GetBloomBits(ctx context.Context, odr OdrBackend, bitIdx uint, sectionIdxLi for i, sectionIdx := range sectionIdxList { sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*BloomTrieFrequency-1) // if we don't have the canonical hash stored for this section head number, we'll still look for - // an entry with a zero sectionHead (we store it with zero section head too if we don't know it + // an entry with a zero SectionHead (we store it with zero section head too if we don't know it // at the time of the retrieval) bloomBits, err := rawdb.ReadBloomBits(db, bitIdx, sectionIdx, sectionHead) if err == nil { diff --git a/light/postprocess.go b/light/postprocess.go index 2090a9d044..455db98638 100644 --- a/light/postprocess.go +++ b/light/postprocess.go @@ -29,11 +29,18 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) +var ( + ErrNoTrustedCht = errors.New("No trusted canonical hash trie") + ErrNoTrustedBloomTrie = errors.New("No trusted bloom trie") + ErrNoHeader = errors.New("Header not found") + chtPrefix = []byte("chtRoot-") // chtPrefix + chtNum (uint64 big endian) -> trie root hash + ChtTablePrefix = "cht-" +) + const ( // CHTFrequencyClient is the block frequency for creating CHTs on the client side. CHTFrequencyClient = 32768 @@ -47,47 +54,6 @@ const ( HelperTrieProcessConfirmations = 256 // number of confirmations before a HelperTrie is generated ) -// trustedCheckpoint represents a set of post-processed trie roots (CHT and BloomTrie) associated with -// the appropriate section index and head hash. It is used to start light syncing from this checkpoint -// and avoid downloading the entire header chain while still being able to securely access old headers/logs. -type trustedCheckpoint struct { - name string - sectionIdx uint64 - sectionHead, chtRoot, bloomTrieRoot common.Hash -} - -var ( - mainnetCheckpoint = trustedCheckpoint{ - name: "mainnet", - sectionIdx: 179, - sectionHead: common.HexToHash("ae778e455492db1183e566fa0c67f954d256fdd08618f6d5a393b0e24576d0ea"), - chtRoot: common.HexToHash("646b338f9ca74d936225338916be53710ec84020b89946004a8605f04c817f16"), - bloomTrieRoot: common.HexToHash("d0f978f5dbc86e5bf931d8dd5b2ecbebbda6dc78f8896af6a27b46a3ced0ac25"), - } - - ropstenCheckpoint = trustedCheckpoint{ - name: "ropsten", - sectionIdx: 107, - sectionHead: common.HexToHash("e1988f95399debf45b873e065e5cd61b416ef2e2e5deec5a6f87c3127086e1ce"), - chtRoot: common.HexToHash("15cba18e4de0ab1e95e202625199ba30147aec8b0b70384b66ebea31ba6a18e0"), - bloomTrieRoot: common.HexToHash("e00fa6389b2e597d9df52172cd8e936879eed0fca4fa59db99e2c8ed682562f2"), - } -) - -// trustedCheckpoints associates each known checkpoint with the genesis hash of the chain it belongs to -var trustedCheckpoints = map[common.Hash]trustedCheckpoint{ - params.MainnetGenesisHash: mainnetCheckpoint, - params.TestnetGenesisHash: ropstenCheckpoint, -} - -var ( - ErrNoTrustedCht = errors.New("No trusted canonical hash trie") - ErrNoTrustedBloomTrie = errors.New("No trusted bloom trie") - ErrNoHeader = errors.New("Header not found") - chtPrefix = []byte("chtRoot-") // chtPrefix + chtNum (uint64 big endian) -> trie root hash - ChtTablePrefix = "cht-" -) - // ChtNode structures are stored in the Canonical Hash Trie in an RLP encoded format type ChtNode struct { Hash common.Hash @@ -95,7 +61,7 @@ type ChtNode struct { } // GetChtRoot reads the CHT root assoctiated to the given section from the database -// Note that sectionIdx is specified according to LES/1 CHT section size +// Note that SectionIdx is specified according to LES/1 CHT section size func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash { var encNumber [8]byte binary.BigEndian.PutUint64(encNumber[:], sectionIdx) @@ -104,13 +70,13 @@ func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) c } // GetChtV2Root reads the CHT root assoctiated to the given section from the database -// Note that sectionIdx is specified according to LES/2 CHT section size +// Note that SectionIdx is specified according to LES/2 CHT section size func GetChtV2Root(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash { return GetChtRoot(db, (sectionIdx+1)*(CHTFrequencyClient/CHTFrequencyServer)-1, sectionHead) } // StoreChtRoot writes the CHT root assoctiated to the given section into the database -// Note that sectionIdx is specified according to LES/1 CHT section size +// Note that SectionIdx is specified according to LES/1 CHT section size func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) { var encNumber [8]byte binary.BigEndian.PutUint64(encNumber[:], sectionIdx) diff --git a/node/node.go b/node/node.go index ada3837217..58f9141d2a 100644 --- a/node/node.go +++ b/node/node.go @@ -48,8 +48,9 @@ type Node struct { serverConfig p2p.Config server *p2p.Server // Currently running P2P networking layer - serviceFuncs []ServiceConstructor // Service constructors (in dependency order) - services map[reflect.Type]Service // Currently running services + serviceFuncs []ServiceConstructor // Service constructors (in dependency order) + callbacks map[reflect.Type][]ServiceCallback // Service callback functions + services map[reflect.Type]Service // Currently running services rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests @@ -113,6 +114,7 @@ func New(conf *Config) (*Node, error) { ephemeralKeystore: ephemeralKeystore, config: conf, serviceFuncs: []ServiceConstructor{}, + callbacks: make(map[reflect.Type][]ServiceCallback), ipcEndpoint: conf.IPCEndpoint(), httpEndpoint: conf.HTTPEndpoint(), wsEndpoint: conf.WSEndpoint(), @@ -134,6 +136,18 @@ func (n *Node) Register(constructor ServiceConstructor) error { return nil } +// RegisterCallback injects a callback function associated with the specified service. +func (n *Node) RegisterCallback(typ reflect.Type, callback ServiceCallback) error { + n.lock.Lock() + defer n.lock.Unlock() + + if n.server != nil { + return ErrNodeRunning + } + n.callbacks[typ] = append(n.callbacks[typ], callback) + return nil +} + // Start create a live P2P node and starts running it. func (n *Node) Start() error { n.lock.Lock() @@ -211,7 +225,7 @@ func (n *Node) Start() error { // Mark the service started for potential cleanup started = append(started, kind) } - // Lastly start the configured RPC interfaces + // Start the configured RPC interfaces if err := n.startRPC(services); err != nil { for _, service := range services { service.Stop() @@ -223,7 +237,21 @@ func (n *Node) Start() error { n.services = services n.server = running n.stop = make(chan struct{}) - + // Lastly invokes all registered services callbacks after server is started. + for typ, callbacks := range n.callbacks { + if service, ok := services[typ]; ok { + for _, callback := range callbacks { + if err := callback(service); err != nil { + for _, service := range services { + service.Stop() + } + running.Stop() + n.services, n.server, n.stop = nil, nil, nil + return err + } + } + } + } return nil } @@ -480,7 +508,12 @@ func (n *Node) Restart() error { func (n *Node) Attach() (*rpc.Client, error) { n.lock.RLock() defer n.lock.RUnlock() + return n.AttachLocked() +} +// AttachLocked creates an RPC client attached to an in-process API handler. +// Note, this function assumes the lock of node is held. +func (n *Node) AttachLocked() (*rpc.Client, error) { if n.server == nil { return nil, ErrNodeStopped } diff --git a/node/service.go b/node/service.go index 6a96d9b1e1..ae65e10d4f 100644 --- a/node/service.go +++ b/node/service.go @@ -71,6 +71,10 @@ func (ctx *ServiceContext) Service(service interface{}) error { // registered for service instantiation. type ServiceConstructor func(ctx *ServiceContext) (Service, error) +// ServiceCallback is the function signature of the callbacks needed to be invoked +// after associated service is started. +type ServiceCallback func(service Service) error + // Service is an individual protocol that can be registered into a node. // // Notes: diff --git a/params/config.go b/params/config.go index b9e9bb8d6e..70a1edead4 100644 --- a/params/config.go +++ b/params/config.go @@ -27,6 +27,7 @@ import ( var ( MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") TestnetGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d") + RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177") ) var (