diff --git a/contracts/checkpointoracle/oracle.go b/contracts/checkpointoracle/oracle.go index 2d725397b..1f273272a 100644 --- a/contracts/checkpointoracle/oracle.go +++ b/contracts/checkpointoracle/oracle.go @@ -29,8 +29,9 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -// CheckpointOracle is a Go wrapper around an on-chain light client checkpoint oracle. +// CheckpointOracle is a Go wrapper around an on-chain checkpoint oracle contract. type CheckpointOracle struct { + address common.Address contract *contract.CheckpointOracle } @@ -40,7 +41,12 @@ func NewCheckpointOracle(contractAddr common.Address, backend bind.ContractBacke if err != nil { return nil, err } - return &CheckpointOracle{contract: c}, nil + return &CheckpointOracle{address: contractAddr, contract: c}, nil +} + +// ContractAddr returns the address of contract. +func (oracle *CheckpointOracle) ContractAddr() common.Address { + return oracle.address } // Contract returns the underlying contract instance. diff --git a/go.sum b/go.sum index edbb5ea2e..4d18c8c20 100644 --- a/go.sum +++ b/go.sum @@ -101,6 +101,7 @@ github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 h1:FSeK4fZCo github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 h1:F/iKcka0K2LgnKy/fgSBf235AETtm1n1TvBzqu40LE0= github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= diff --git a/les/api.go b/les/api.go index ad511c9d6..f9b8c3445 100644 --- a/les/api.go +++ b/les/api.go @@ -350,5 +350,5 @@ func (api *PrivateLightAPI) GetCheckpointContractAddress() (string, error) { if api.backend.oracle == nil { return "", errNotActivated } - return api.backend.oracle.config.Address.Hex(), nil + return api.backend.oracle.Contract().ContractAddr().Hex(), nil } diff --git a/les/checkpointoracle.go b/les/checkpointoracle/oracle.go similarity index 65% rename from les/checkpointoracle.go rename to les/checkpointoracle/oracle.go index 5494e3d6d..c3983e1a9 100644 --- a/les/checkpointoracle.go +++ b/les/checkpointoracle/oracle.go @@ -14,7 +14,10 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package les +// Package checkpointoracle is a wrapper of checkpoint oracle contract with +// additional rules defined. This package can be used both in LES client or +// server side for offering oracle related APIs. +package checkpointoracle import ( "encoding/binary" @@ -28,10 +31,10 @@ import ( "github.com/ethereum/go-ethereum/params" ) -// checkpointOracle is responsible for offering the latest stable checkpoint -// generated and announced by the contract admins on-chain. The checkpoint is -// verified by clients locally during the checkpoint syncing. -type checkpointOracle struct { +// CheckpointOracle is responsible for offering the latest stable checkpoint +// generated and announced by the contract admins on-chain. The checkpoint can +// be verified by clients locally during the checkpoint syncing. +type CheckpointOracle struct { config *params.CheckpointOracleConfig contract *checkpointoracle.CheckpointOracle @@ -39,8 +42,8 @@ type checkpointOracle struct { getLocal func(uint64) params.TrustedCheckpoint // Function used to retrieve local checkpoint } -// newCheckpointOracle returns a checkpoint registrar handler. -func newCheckpointOracle(config *params.CheckpointOracleConfig, getLocal func(uint64) params.TrustedCheckpoint) *checkpointOracle { +// New creates a checkpoint oracle handler with given configs and callback. +func New(config *params.CheckpointOracleConfig, getLocal func(uint64) params.TrustedCheckpoint) *CheckpointOracle { if config == nil { log.Info("Checkpoint registrar is not enabled") return nil @@ -51,41 +54,46 @@ func newCheckpointOracle(config *params.CheckpointOracleConfig, getLocal func(ui } log.Info("Configured checkpoint registrar", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold) - return &checkpointOracle{ + return &CheckpointOracle{ config: config, getLocal: getLocal, } } -// start binds the registrar contract and start listening to the -// newCheckpointEvent for the server side. -func (reg *checkpointOracle) start(backend bind.ContractBackend) { - contract, err := checkpointoracle.NewCheckpointOracle(reg.config.Address, backend) +// Start binds the contract backend, initializes the oracle instance +// and marks the status as available. +func (oracle *CheckpointOracle) Start(backend bind.ContractBackend) { + contract, err := checkpointoracle.NewCheckpointOracle(oracle.config.Address, backend) if err != nil { log.Error("Oracle contract binding failed", "err", err) return } - if !atomic.CompareAndSwapInt32(®.running, 0, 1) { + if !atomic.CompareAndSwapInt32(&oracle.running, 0, 1) { log.Error("Already bound and listening to registrar") return } - reg.contract = contract + oracle.contract = contract } -// isRunning returns an indicator whether the registrar is running. -func (reg *checkpointOracle) isRunning() bool { - return atomic.LoadInt32(®.running) == 1 +// IsRunning returns an indicator whether the oracle is running. +func (oracle *CheckpointOracle) IsRunning() bool { + return atomic.LoadInt32(&oracle.running) == 1 } -// stableCheckpoint returns the stable checkpoint which was generated by local +// Contract returns the underlying raw checkpoint oracle contract. +func (oracle *CheckpointOracle) Contract() *checkpointoracle.CheckpointOracle { + return oracle.contract +} + +// StableCheckpoint returns the stable checkpoint which was generated by local // indexers and announced by trusted signers. -func (reg *checkpointOracle) stableCheckpoint() (*params.TrustedCheckpoint, uint64) { +func (oracle *CheckpointOracle) StableCheckpoint() (*params.TrustedCheckpoint, uint64) { // Retrieve the latest checkpoint from the contract, abort if empty - latest, hash, height, err := reg.contract.Contract().GetLatestCheckpoint(nil) + latest, hash, height, err := oracle.contract.Contract().GetLatestCheckpoint(nil) if err != nil || (latest == 0 && hash == [32]byte{}) { return nil, 0 } - local := reg.getLocal(latest) + local := oracle.getLocal(latest) // The following scenarios may occur: // @@ -93,19 +101,18 @@ func (reg *checkpointOracle) stableCheckpoint() (*params.TrustedCheckpoint, uint // checkpoint which registered in the contract. // * local checkpoint doesn't match with the registered one. // - // In both cases, server won't send the **stable** checkpoint - // to the client(no worry, client can use hardcoded one instead). - if local.HashEqual(common.Hash(hash)) { + // In both cases, no stable checkpoint will be returned. + if local.HashEqual(hash) { return &local, height.Uint64() } return nil, 0 } -// verifySigners recovers the signer addresses according to the signature and +// VerifySigners recovers the signer addresses according to the signature and // checks whether there are enough approvals to finalize the checkpoint. -func (reg *checkpointOracle) verifySigners(index uint64, hash [32]byte, signatures [][]byte) (bool, []common.Address) { +func (oracle *CheckpointOracle) VerifySigners(index uint64, hash [32]byte, signatures [][]byte) (bool, []common.Address) { // Short circuit if the given signatures doesn't reach the threshold. - if len(signatures) < int(reg.config.Threshold) { + if len(signatures) < int(oracle.config.Threshold) { return false, nil } var ( @@ -128,7 +135,7 @@ func (reg *checkpointOracle) verifySigners(index uint64, hash [32]byte, signatur // hash = keccak256(checkpoint_index, section_head, cht_root, bloom_root) buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, index) - data := append([]byte{0x19, 0x00}, append(reg.config.Address.Bytes(), append(buf, hash[:]...)...)...) + data := append([]byte{0x19, 0x00}, append(oracle.config.Address.Bytes(), append(buf, hash[:]...)...)...) signatures[i][64] -= 27 // Transform V from 27/28 to 0/1 according to the yellow paper for verification. pubkey, err := crypto.Ecrecover(crypto.Keccak256(data), signatures[i]) if err != nil { @@ -139,14 +146,14 @@ func (reg *checkpointOracle) verifySigners(index uint64, hash [32]byte, signatur if _, exist := checked[signer]; exist { continue } - for _, s := range reg.config.Signers { + for _, s := range oracle.config.Signers { if s == signer { signers = append(signers, signer) checked[signer] = struct{}{} } } } - threshold := reg.config.Threshold + threshold := oracle.config.Threshold if uint64(len(signers)) < threshold { log.Warn("Not enough signers to approve checkpoint", "signers", len(signers), "threshold", threshold) return false, nil diff --git a/les/client.go b/les/client.go index c460f4c09..36d5a8d2f 100644 --- a/les/client.go +++ b/les/client.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/les/checkpointoracle" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -123,7 +124,7 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { if oracle == nil { oracle = params.CheckpointOracles[genesisHash] } - leth.oracle = newCheckpointOracle(oracle, leth.localCheckpoint) + leth.oracle = checkpointoracle.New(oracle, leth.localCheckpoint) // Note: AddChildIndexer starts the update process for the child leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer) @@ -275,5 +276,5 @@ func (s *LightEthereum) SetContractBackend(backend bind.ContractBackend) { if s.oracle == nil { return } - s.oracle.start(backend) + s.oracle.Start(backend) } diff --git a/les/commons.go b/les/commons.go index ad3c5aef3..b402c5176 100644 --- a/les/commons.go +++ b/les/commons.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/les/checkpointoracle" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discv5" @@ -63,7 +64,7 @@ type lesCommons struct { peers *peerSet chainReader chainReader chtIndexer, bloomTrieIndexer *core.ChainIndexer - oracle *checkpointOracle + oracle *checkpointoracle.CheckpointOracle closeCh chan struct{} wg sync.WaitGroup diff --git a/les/peer.go b/les/peer.go index 801147df6..feb3910be 100644 --- a/les/peer.go +++ b/les/peer.go @@ -616,8 +616,8 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis // Add advertised checkpoint and register block height which // client can verify the checkpoint validity. - if server.oracle != nil && server.oracle.isRunning() { - cp, height := server.oracle.stableCheckpoint() + if server.oracle != nil && server.oracle.IsRunning() { + cp, height := server.oracle.StableCheckpoint() if cp != nil { send = send.add("checkpoint/value", cp) send = send.add("checkpoint/registerHeight", height) diff --git a/les/server.go b/les/server.go index e68903dd8..664eba971 100644 --- a/les/server.go +++ b/les/server.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/les/checkpointoracle" "github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" @@ -96,7 +97,7 @@ func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) { if oracle == nil { oracle = params.CheckpointOracles[e.BlockChain().Genesis().Hash()] } - srv.oracle = newCheckpointOracle(oracle, srv.localCheckpoint) + srv.oracle = checkpointoracle.New(oracle, srv.localCheckpoint) // Initialize server capacity management fields. srv.defParams = flowcontrol.ServerParams{ @@ -216,7 +217,7 @@ func (s *LesServer) SetContractBackend(backend bind.ContractBackend) { if s.oracle == nil { return } - s.oracle.start(backend) + s.oracle.Start(backend) } // capacityManagement starts an event handler loop that updates the recharge curve of diff --git a/les/sync.go b/les/sync.go index 1214fefca..207686403 100644 --- a/les/sync.go +++ b/les/sync.go @@ -66,7 +66,7 @@ func (h *clientHandler) validateCheckpoint(peer *peer) error { if err != nil { return err } - events := h.backend.oracle.contract.LookupCheckpointEvents(logs, cp.SectionIndex, cp.Hash()) + events := h.backend.oracle.Contract().LookupCheckpointEvents(logs, cp.SectionIndex, cp.Hash()) if len(events) == 0 { return errInvalidCheckpoint } @@ -78,7 +78,7 @@ func (h *clientHandler) validateCheckpoint(peer *peer) error { for _, event := range events { signatures = append(signatures, append(event.R[:], append(event.S[:], event.V)...)) } - valid, signers := h.backend.oracle.verifySigners(index, hash, signatures) + valid, signers := h.backend.oracle.VerifySigners(index, hash, signatures) if !valid { return errInvalidCheckpoint } @@ -134,7 +134,7 @@ func (h *clientHandler) synchronise(peer *peer) { case hardcoded: mode = legacyCheckpointSync log.Debug("Disable checkpoint syncing", "reason", "checkpoint is hardcoded") - case h.backend.oracle == nil || !h.backend.oracle.isRunning(): + case h.backend.oracle == nil || !h.backend.oracle.IsRunning(): if h.checkpoint == nil { mode = lightSync // Downgrade to light sync unfortunately. } else { diff --git a/les/sync_test.go b/les/sync_test.go index 8df6223b8..1c157b4fb 100644 --- a/les/sync_test.go +++ b/les/sync_test.go @@ -80,14 +80,14 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - if _, err := server.handler.server.oracle.contract.RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { + if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { t.Error("register checkpoint failed", err) } server.backend.Commit() // Wait for the checkpoint registration for { - _, hash, _, err := server.handler.server.oracle.contract.Contract().GetLatestCheckpoint(nil) + _, hash, _, err := server.handler.server.oracle.Contract().Contract().GetLatestCheckpoint(nil) if err != nil || hash == [32]byte{} { time.Sleep(10 * time.Millisecond) continue @@ -164,14 +164,14 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - if _, err := server.handler.server.oracle.contract.RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { + if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { t.Error("register checkpoint failed", err) } server.backend.Commit() // Wait for the checkpoint registration for { - _, hash, _, err := server.handler.server.oracle.contract.Contract().GetLatestCheckpoint(nil) + _, hash, _, err := server.handler.server.oracle.Contract().Contract().GetLatestCheckpoint(nil) if err != nil || hash == [32]byte{} { time.Sleep(100 * time.Millisecond) continue diff --git a/les/test_helper.go b/les/test_helper.go index ee3d7a32e..fb1965eeb 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -39,6 +39,7 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/les/checkpointoracle" "github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/p2p" @@ -174,7 +175,7 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}}, GasLimit: 100000000, } - oracle *checkpointOracle + oracle *checkpointoracle.CheckpointOracle ) genesis := gspec.MustCommit(db) chain, _ := light.NewLightChain(odr, gspec.Config, engine, nil) @@ -194,7 +195,7 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index BloomRoot: light.GetBloomTrieRoot(db, index, sectionHead), } } - oracle = newCheckpointOracle(checkpointConfig, getLocal) + oracle = checkpointoracle.New(checkpointConfig, getLocal) } client := &LightEthereum{ lesCommons: lesCommons{ @@ -218,7 +219,7 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index client.handler = newClientHandler(ulcServers, ulcFraction, nil, client) if client.oracle != nil { - client.oracle.start(backend) + client.oracle.Start(backend) } return client.handler } @@ -230,7 +231,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}}, GasLimit: 100000000, } - oracle *checkpointOracle + oracle *checkpointoracle.CheckpointOracle ) genesis := gspec.MustCommit(db) @@ -257,7 +258,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da BloomRoot: light.GetBloomTrieRoot(db, index, sectionHead), } } - oracle = newCheckpointOracle(checkpointConfig, getLocal) + oracle = checkpointoracle.New(checkpointConfig, getLocal) } server := &LesServer{ lesCommons: lesCommons{ @@ -284,7 +285,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da server.clientPool.setLimits(10000, 10000) // Assign enough capacity for clientpool server.handler = newServerHandler(server, simulation.Blockchain(), db, txpool, func() bool { return true }) if server.oracle != nil { - server.oracle.start(simulation) + server.oracle.Start(simulation) } server.servingQueue.setThreads(4) server.handler.start()