diff options
author | Péter Szilágyi <peterke@gmail.com> | 2016-03-31 17:20:24 +0800 |
---|---|---|
committer | Péter Szilágyi <peterke@gmail.com> | 2016-05-02 21:20:04 +0800 |
commit | d46da273c6731512b4114393856a96be06505797 (patch) | |
tree | 9388efc741e1c95fae7be73de0d8a6084fec5491 | |
parent | ecd7199c4367bbffd5000ab6ee9bad3ef88de5d2 (diff) | |
download | go-tangerine-d46da273c6731512b4114393856a96be06505797.tar.gz go-tangerine-d46da273c6731512b4114393856a96be06505797.tar.zst go-tangerine-d46da273c6731512b4114393856a96be06505797.zip |
common/releases: rewrite release version contract + use native dapps
-rw-r--r-- | common/versions/version.sol | 152 | ||||
-rw-r--r-- | common/versions/versions.go | 215 | ||||
-rw-r--r-- | contracts/release/contract.go | 474 | ||||
-rw-r--r-- | contracts/release/contract.sol | 240 | ||||
-rw-r--r-- | contracts/release/contract_test.go | 374 | ||||
-rw-r--r-- | contracts/release/generator.go | 19 |
6 files changed, 1107 insertions, 367 deletions
diff --git a/common/versions/version.sol b/common/versions/version.sol deleted file mode 100644 index 68c883115..000000000 --- a/common/versions/version.sol +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2015 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 <http://www.gnu.org/licenses/>. - -// WARNING: WORK IN PROGRESS & UNTESTED -// -// contract tracking versions added by designated signers. -// designed to track versions of geth (go-ethereum) recommended by the -// go-ethereum team. geth client interfaces with contract through ABI by simply -// reading the full state and then deciding on recommended version based on -// some logic (e.g. version date & number of signers). -// -// to keep things simple, the contract does not use FSM for multisig -// but rather allows any designated signer to add a version or vote for an -// existing version. this avoids need to track voting-in-progress states and -// also provides history of all past versions. -// - -contract Versions { - struct V { - bytes32 v; - uint64 ts; - address[] signers; - } - - address[] public parties; // owners/signers - address[] public deleteAcks; // votes to suicide contract - uint public deleteAcksReq; // number of votes needed - V[] public versions; - - modifier canAccess(address addr) { - bool access = false; - for (uint i = 0; i < parties.length; i++) { - if (parties[i] == addr) { - access = true; - break; - } - } - if (access == false) { - throw; - } - _ - } - - function Versions(address[] addrs) { - if (addrs.length < 2) { - throw; - } - - parties = addrs; - deleteAcksReq = (addrs.length / 2) + 1; - } - - // TODO: use dynamic array when solidity adds proper support for returning them - function GetVersions() returns (bytes32[10], uint64[10], uint[10]) { - bytes32[10] memory vs; - uint64[10] memory ts; - uint[10] memory ss; - for (uint i = 0; i < versions.length; i++) { - vs[i] = versions[i].v; - ts[i] = versions[i].ts; - ss[i] = versions[i].signers.length; - } - return (vs, ts, ss); - } - - // either submit a new version or acknowledge an existing one - function AckVersion(bytes32 ver) - canAccess(msg.sender) - { - for (uint i = 0; i < versions.length; i++) { - if (versions[i].v == ver) { - for (uint j = 0; j < versions[i].signers.length; j++) { - if (versions[i].signers[j] == msg.sender) { - // already signed - throw; - } - } - // add sender as signer of existing version - versions[i].signers.push(msg.sender); - return; - } - } - - // version is new, add it - // due to dynamic array, push it first then set values - V memory v; - versions.push(v); - versions[versions.length - 1].v = ver; - // signers is dynamic array; have to extend size manually - versions[versions.length - 1].signers.length++; - versions[versions.length - 1].signers[0] = msg.sender; - versions[versions.length - 1].ts = uint64(block.timestamp); - } - - // remove vote for a version, if present - function NackVersion(bytes32 ver) - canAccess(msg.sender) - { - for (uint i = 0; i < versions.length; i++) { - if (versions[i].v == ver) { - for (uint j = 0; j < versions[i].signers.length; j++) { - if (versions[i].signers[j] == msg.sender) { - delete versions[i].signers[j]; - } - } - } - } - } - - // delete-this-contract vote, suicide if enough votes - function AckDelete() - canAccess(msg.sender) - { - for (uint i = 0; i < deleteAcks.length; i++) { - if (deleteAcks[i] == msg.sender) { - throw; // already acked delete - } - } - deleteAcks.push(msg.sender); - if (deleteAcks.length >= deleteAcksReq) { - suicide(msg.sender); - } - } - - // remove sender's delete-this-contract vote, if present - function NackDelete() - canAccess(msg.sender) - { - uint len = deleteAcks.length; - for (uint i = 0; i < len; i++) { - if (deleteAcks[i] == msg.sender) { - if (len > 1) { - deleteAcks[i] = deleteAcks[len-1]; - } - deleteAcks.length -= 1; - } - } - } -} diff --git a/common/versions/versions.go b/common/versions/versions.go deleted file mode 100644 index dc7681485..000000000 --- a/common/versions/versions.go +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2015 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 <http://www.gnu.org/licenses/>. - -package versions - -import ( - "fmt" - "math/big" - "strconv" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rpc" -) - -var ( - jsonlogger = logger.NewJsonLogger() - // TODO: add Frontier address - GlobalVersionsAddr = common.HexToAddress("0x40bebcadbb4456db23fda39f261f3b2509096e9e") // test - dummySender = common.HexToAddress("0x16db48070243bc37a1c59cd5bb977ad7047618be") // test - getVersionsSignature = "GetVersions()" - firstCheckTime = time.Second * 4 - continousCheckTime = time.Second * 600 -) - -type VersionCheck struct { - serverName string - timer *time.Timer - e *eth.Ethereum - stop chan bool -} - -// Boilerplate to satisfy node.Service interface -func (v *VersionCheck) Protocols() []p2p.Protocol { - return []p2p.Protocol{} -} - -func (v *VersionCheck) APIs() []rpc.API { - return []rpc.API{} -} - -func (v *VersionCheck) Start(server *p2p.Server) error { - v.serverName = server.Name - // Check version first time after a few seconds so it shows after - // other startup messages - t := time.NewTimer(firstCheckTime) - v.timer = t - v.stop = make(chan bool) - versionCheck := func() { - for { - select { - case <-v.stop: - close(v.stop) - return - case <-v.timer.C: - _, err := get(v.e, v.serverName) - if err != nil { - glog.V(logger.Error).Infof("Could not query geth version contract: %s", err) - } - v.timer.Reset(continousCheckTime) - } - } - } - go versionCheck() - return nil -} - -func (v *VersionCheck) Stop() error { - v.stop <- true - select { - case <-v.stop: - } - return nil -} - -func NewVersionCheck(ctx *node.ServiceContext) (node.Service, error) { - var v VersionCheck - var e *eth.Ethereum - // sets e to the Ethereum instance previously started - // expects double pointer - ctx.Service(&e) - v.e = e - return &v, nil -} - -// query versions list from the (custom) accessor in the versions contract -func get(e *eth.Ethereum, clientVersion string) (string, error) { - // TODO: move common/registrar abiSignature to some util package - abi := crypto.Sha3([]byte(getVersionsSignature))[:4] - res, _, err := simulateCall( - e, - &dummySender, - &GlobalVersionsAddr, - big.NewInt(3000000), // gasLimit - big.NewInt(1), // gasPrice - big.NewInt(0), // value - abi) - if err != nil { - return "", err - } - - // TODO: we use static arrays of size versionCount as workaround - // until solidity has proper support for returning dynamic arrays - versionCount := 10 - - if len(res) != 2+(64*versionCount*3) { // 0x + three 32-byte fields per version - return "", fmt.Errorf("unexpected result length from GetVersions") - } - - // TODO: use ABI (after solidity supports returning arrays of arrays and/or structs) - var versions []string - var timestamps []uint64 - var signerCounts []uint64 - - // trim 0x - res = res[2:] - - // parse res - for i := 0; i < versionCount; i++ { - bytes := common.FromHex(res[:64]) - versions = append(versions, string(bytes)) - res = res[64:] - } - - for i := 0; i < versionCount; i++ { - ts, err := strconv.ParseUint(res[:64], 16, 64) - if err != nil { - return "", err - } - timestamps = append(timestamps, ts) - res = res[64:] - } - - for i := 0; i < versionCount; i++ { - sc, err := strconv.ParseUint(res[:64], 16, 64) - if err != nil { - return "", err - } - signerCounts = append(signerCounts, sc) - res = res[64:] - } - - // TODO: version matching logic (e.g. most votes / most recent) - if versions[0] != clientVersion { - glog.V(logger.Info).Infof("geth version %s does not match recommended version %s", clientVersion, versions[0]) - } - - return res, nil -} - -func simulateCall(e *eth.Ethereum, from0, to *common.Address, gas, gasPrice, value *big.Int, data []byte) (string, *big.Int, error) { - stateCopy, err := e.BlockChain().State() - if err != nil { - return "", nil, err - } - from := stateCopy.GetOrNewStateObject(*from0) - from.SetBalance(common.MaxBig) - - msg := callmsg{ - from: from, - to: to, - gas: gas, - gasPrice: gasPrice, - value: value, - data: data, - } - - // Execute the call and return - vmenv := core.NewEnv(stateCopy, e.BlockChain(), msg, e.BlockChain().CurrentHeader()) - gp := new(core.GasPool).AddGas(common.MaxBig) - - res, gas, err := core.ApplyMessage(vmenv, msg, gp) - return common.ToHex(res), gas, err - -} - -// TODO: consider moving to package common or accounts/abi as it's useful for anyone -// simulating EVM CALL -type callmsg struct { - from *state.StateObject - to *common.Address - gas, gasPrice *big.Int - value *big.Int - data []byte -} - -// accessor boilerplate to implement core.Message -func (m callmsg) From() (common.Address, error) { return m.from.Address(), nil } -func (m callmsg) Nonce() uint64 { return m.from.Nonce() } -func (m callmsg) To() *common.Address { return m.to } -func (m callmsg) GasPrice() *big.Int { return m.gasPrice } -func (m callmsg) Gas() *big.Int { return m.gas } -func (m callmsg) Value() *big.Int { return m.value } -func (m callmsg) Data() []byte { return m.data } diff --git a/contracts/release/contract.go b/contracts/release/contract.go new file mode 100644 index 000000000..ffdcc14cd --- /dev/null +++ b/contracts/release/contract.go @@ -0,0 +1,474 @@ +// This file is an automatically generated Go binding. Do not modify as any +// change will likely be lost upon the next re-generation! + +package release + +import ( + "math/big" + "strings" + + "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" +) + +// ReleaseOracleABI is the input ABI used to generate the binding from. +const ReleaseOracleABI = `[{"constant":false,"inputs":[],"name":"Nuke","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"major","type":"uint32"},{"name":"minor","type":"uint32"},{"name":"patch","type":"uint32"},{"name":"commit","type":"bytes20"}],"name":"Release","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"AuthProposals","outputs":[{"name":"","type":"address[]"}],"type":"function"},{"constant":true,"inputs":[],"name":"CurrentVersion","outputs":[{"name":"major","type":"uint32"},{"name":"minor","type":"uint32"},{"name":"patch","type":"uint32"},{"name":"commit","type":"bytes20"},{"name":"time","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"ProposedVersion","outputs":[{"name":"major","type":"uint32"},{"name":"minor","type":"uint32"},{"name":"patch","type":"uint32"},{"name":"commit","type":"bytes20"},{"name":"pass","type":"address[]"},{"name":"fail","type":"address[]"}],"type":"function"},{"constant":false,"inputs":[{"name":"user","type":"address"}],"name":"Promote","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"major","type":"uint32"},{"name":"minor","type":"uint32"},{"name":"patch","type":"uint32"},{"name":"commit","type":"bytes20"},{"name":"release","type":"bool"}],"name":"updateRelease","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"user","type":"address"}],"name":"Demote","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"user","type":"address"}],"name":"AuthVotes","outputs":[{"name":"promote","type":"address[]"},{"name":"demote","type":"address[]"}],"type":"function"},{"constant":true,"inputs":[],"name":"Signers","outputs":[{"name":"","type":"address[]"}],"type":"function"},{"constant":false,"inputs":[{"name":"user","type":"address"},{"name":"authorize","type":"bool"}],"name":"updateSigner","outputs":[],"type":"function"},{"inputs":[],"type":"constructor"}]` + +// ReleaseOracleBin is the compiled bytecode used for deploying new contracts. +const ReleaseOracleBin = `0x6060604052600160a060020a0333166000908152602081905260409020805460ff1916600190811790915580548082018083558281838015829011606257818360005260206000209182019101606291905b808211156094576000815584016051565b505050919090600052602060002090016000508054600160a060020a0319163317905550611262806100986000396000f35b5090566060604052361561008d5760e060020a60003504630443b1ad811461008f5780630d618178146100a0578063282fe4e5146100bd5780632b225f291461012c5780634c327071146101515780636195db9c14610276578063645dce721461028757806380bbbd4a146102d6578063a29226f2146102e7578063f04c4758146103e1578063f460590b1461044d575b005b61008d61072060008080808061029a565b61008d60043560243560443560643561071a84848484600161029a565b6104d360408051602081810183526000825282516003805480840283018401909552848252929390929183018282801561044257602002820191906000526020600020908154600160a060020a0316815260019190910190602001808311610423575b5050505050905061044a565b61051d600060006000600060006000600860005080549050600014156106895761070a565b610551604080516020818101835260008083528351808301855281815260045460068054875181870281018701909852808852939687968796879691959463ffffffff818116956401000000008304821695604060020a840490921694606060020a93849004909302939092600792918491908301828280156101fe57602002820191906000526020600020905b8154600160a060020a03168152600191909101906020018083116101df575b505050505091508080548060200260200160405190810160405280929190818152602001828054801561025b57602002820191906000526020600020905b8154600160a060020a031681526001919091019060200180831161023c575b50505050509050955095509550955095509550909192939495565b61008d600435610712816001610457565b61008d6004356024356044356064356084355b600160a060020a033316600090815260208190526040812054819060ff16156111f957821580156102cc575060065481145b15610c81576111f9565b61008d600435610712816000610457565b6106046004356040805160208181018352600080835283518083018552818152600160a060020a038616825260028352908490208054855181850281018501909652808652939491939092600184019291849183018282801561037457602002820191906000526020600020905b8154600160a060020a0316815260019190910190602001808311610355575b50505050509150808054806020026020016040519081016040528092919081815260200182805480156103d157602002820191906000526020600020905b8154600160a060020a03168152600191909101906020018083116103b2575b5050505050905091509150915091565b604080516020818101835260008252600180548451818402810184019095528085526104d3949283018282801561044257602002820191906000526020600020905b8154600160a060020a0316815260019190910190602001808311610423575b505050505090505b90565b61008d6004356024355b600160a060020a033316600090815260208190526040812054819060ff161561071a57600160a060020a038416815260026020526040812091505b8154811015610722578154600160a060020a033316908390839081101561000257600091825260209091200154600160a060020a0316141561076d5761071a565b60405180806020018281038252838181518152602001915080519060200190602002808383829060006004602084601f0104600f02600301f1509050019250505060405180910390f35b6040805163ffffffff9687168152948616602086015294909216838501526060830152608082015290519081900360a00190f35b604051808763ffffffff1681526020018663ffffffff1681526020018563ffffffff16815260200184815260200180602001806020018381038352858181518152602001915080519060200190602002808383829060006004602084601f0104600f02600301f1509050018381038252848181518152602001915080519060200190602002808383829060006004602084601f0104600f02600301f1509050019850505050505050505060405180910390f35b6040518080602001806020018381038352858181518152602001915080519060200190602002808383829060006004602084601f0104600f02600301f1509050018381038252848181518152602001915080519060200190602002808383829060006004602084601f0104600f02600301f15090500194505050505060405180910390f35b600880546000198101908110156100025760009182526004027ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee30190508054600182015463ffffffff8281169950640100000000830481169850604060020a8304169650606060020a91829004909102945067ffffffffffffffff16925090505b509091929394565b50565b505050505b50505050565b565b5060005b60018201548110156107755733600160a060020a03168260010160005082815481101561000257600091825260209091200154600160a060020a031614156107bf5761071a565b600101610492565b8154600014801561078a575060018201546000145b156107e757600380546001810180835582818380158290116107c7578183600052602060002091820191016107c7919061086d565b600101610726565b5050506000928352506020909120018054600160a060020a031916851790555b82156108855781546001810180845583919082818380158290116108ba576000838152602090206108ba91810190830161086d565b5050506000928352506020909120018054600160a060020a031916851790555b600160a060020a038416600090815260026020908152604082208054838255818452918320909291610b6991908101905b80821115610881576000815560010161086d565b5090565b81600101600050805480600101828181548183558181151161097657818360005260206000209182019101610976919061086d565b5050506000928352506020909120018054600160a060020a031916331790556001548254600290910490116108ee5761071a565b8280156109145750600160a060020a03841660009081526020819052604090205460ff16155b156109ad57600160a060020a0384166000908152602081905260409020805460ff191660019081179091558054808201808355828183801582901161081c57600083905261081c9060008051602061124283398151915290810190830161086d565b5050506000928352506020909120018054600160a060020a031916331790556001805490830154600290910490116108ee5761071a565b821580156109d35750600160a060020a03841660009081526020819052604090205460ff165b1561083c5750600160a060020a0383166000908152602081905260408120805460ff191690555b60015481101561083c5783600160a060020a03166001600050828154811015610002576000919091526000805160206112428339815191520154600160a060020a03161415610add576001805460001981019081101561000257600091825260008051602061124283398151915201909054906101000a9004600160a060020a0316600160005082815481101561000257600080516020611242833981519152018054600160a060020a03191690921790915580546000198101808355909190828015829011610ae557818360005260206000209182019101610ae5919061086d565b6001016109fa565b5050600060048181556005805467ffffffffffffffff19169055600680548382558184529194509192508290610b3f907ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f9081019061086d565b5060018201805460008083559182526020909120610b5f9181019061086d565b505050505061083c565b5060018201805460008083559182526020909120610b899181019061086d565b506000925050505b60035481101561071a5783600160a060020a03166003600050828154811015610002576000919091526000805160206112228339815191520154600160a060020a03161415610c79576003805460001981019081101561000257600091825260008051602061122283398151915201909054906101000a9004600160a060020a0316600360005082815481101561000257600080516020611222833981519152018054600160a060020a03191690921790915580546000198101808355909190828015829011610715576107159060008051602061122283398151915290810190830161086d565b600101610b91565b60065460001415610cdf576004805463ffffffff1916881767ffffffff0000000019166401000000008802176bffffffff00000000000000001916604060020a8702176bffffffffffffffffffffffff16606060020a808704021790555b828015610d49575060045463ffffffff8881169116141580610d155750600454640100000000900463ffffffff90811690871614155b80610d32575060045463ffffffff808716604060020a9092041614155b80610d495750600454606060020a90819004028414155b15610d53576111f9565b506006905060005b8154811015610d9c578154600160a060020a033316908390839081101561000257600091825260209091200154600160a060020a03161415610de7576111f9565b5060005b6001820154811015610def5733600160a060020a03168260010160005082815481101561000257600091825260209091200154600160a060020a03161415610e26576111f9565b600101610d5b565b8215610e2e578154600181018084558391908281838015829011610e6357818360005260206000209182019101610e63919061086d565b600101610da0565b816001016000508054806001018281815481835581811511610ee657818360005260206000209182019101610ee6919061086d565b5050506000928352506020909120018054600160a060020a03191633179055600154825460029091049011610e97576111f9565b8215610f1d576005805467ffffffffffffffff19164217905560088054600181018083558281838015829011610f7257600402816004028360005260206000209182019101610f72919061108b565b5050506000928352506020909120018054600160a060020a03191633179055600180549083015460029091049011610e97576111f9565b600060048181556005805467ffffffffffffffff19169055600680548382558184529192918290611202907ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f9081019061086d565b5050509190906000526020600020906004020160005060048054825463ffffffff191663ffffffff9182161780845582546401000000009081900483160267ffffffff000000001991909116178084558254604060020a908190049092169091026bffffffff00000000000000001991909116178083558154606060020a908190048102819004026bffffffffffffffffffffffff9190911617825560055460018301805467ffffffffffffffff191667ffffffffffffffff9092169190911790556006805460028401805482825560008281526020902094959491928392918201918582156110ea5760005260206000209182015b828111156110ea578254825591600101919060010190611068565b505050506001015b8082111561088157600080825560018201805467ffffffffffffffff191690556002820180548282558183526020832083916110ca919081019061086d565b50600182018054600080835591825260209091206110839181019061086d565b506111109291505b80821115610881578054600160a060020a03191681556001016110f2565b505060018181018054918401805480835560008381526020902092938301929091821561115e5760005260206000209182015b8281111561115e578254825591600101919060010190611143565b5061116a9291506110f2565b5050600060048181556005805467ffffffffffffffff19169055600680548382558184529197509195509093508492506111c991507ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f9081019061086d565b50600182018054600080835591825260209091206111e99181019061086d565b50505050506111f9565b50505050505b50505050505050565b50600182018054600080835591825260209091206111f39181019061086d56c2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85bb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6` + +// DeployReleaseOracle deploys a new Ethereum contract, binding an instance of ReleaseOracle to it. +func DeployReleaseOracle(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *ReleaseOracle, error) { + parsed, err := abi.JSON(strings.NewReader(ReleaseOracleABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ReleaseOracleBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ReleaseOracle{ReleaseOracleCaller: ReleaseOracleCaller{contract: contract}, ReleaseOracleTransactor: ReleaseOracleTransactor{contract: contract}}, nil +} + +// ReleaseOracle is an auto generated Go binding around an Ethereum contract. +type ReleaseOracle struct { + ReleaseOracleCaller // Read-only binding to the contract + ReleaseOracleTransactor // Write-only binding to the contract +} + +// ReleaseOracleCaller is an auto generated read-only Go binding around an Ethereum contract. +type ReleaseOracleCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ReleaseOracleTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ReleaseOracleTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ReleaseOracleSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ReleaseOracleSession struct { + Contract *ReleaseOracle // 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 +} + +// ReleaseOracleCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ReleaseOracleCallerSession struct { + Contract *ReleaseOracleCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ReleaseOracleTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ReleaseOracleTransactorSession struct { + Contract *ReleaseOracleTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ReleaseOracleRaw is an auto generated low-level Go binding around an Ethereum contract. +type ReleaseOracleRaw struct { + Contract *ReleaseOracle // Generic contract binding to access the raw methods on +} + +// ReleaseOracleCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ReleaseOracleCallerRaw struct { + Contract *ReleaseOracleCaller // Generic read-only contract binding to access the raw methods on +} + +// ReleaseOracleTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ReleaseOracleTransactorRaw struct { + Contract *ReleaseOracleTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewReleaseOracle creates a new instance of ReleaseOracle, bound to a specific deployed contract. +func NewReleaseOracle(address common.Address, backend bind.ContractBackend) (*ReleaseOracle, error) { + contract, err := bindReleaseOracle(address, backend.(bind.ContractCaller), backend.(bind.ContractTransactor)) + if err != nil { + return nil, err + } + return &ReleaseOracle{ReleaseOracleCaller: ReleaseOracleCaller{contract: contract}, ReleaseOracleTransactor: ReleaseOracleTransactor{contract: contract}}, nil +} + +// NewReleaseOracleCaller creates a new read-only instance of ReleaseOracle, bound to a specific deployed contract. +func NewReleaseOracleCaller(address common.Address, caller bind.ContractCaller) (*ReleaseOracleCaller, error) { + contract, err := bindReleaseOracle(address, caller, nil) + if err != nil { + return nil, err + } + return &ReleaseOracleCaller{contract: contract}, nil +} + +// NewReleaseOracleTransactor creates a new write-only instance of ReleaseOracle, bound to a specific deployed contract. +func NewReleaseOracleTransactor(address common.Address, transactor bind.ContractTransactor) (*ReleaseOracleTransactor, error) { + contract, err := bindReleaseOracle(address, nil, transactor) + if err != nil { + return nil, err + } + return &ReleaseOracleTransactor{contract: contract}, nil +} + +// bindReleaseOracle binds a generic wrapper to an already deployed contract. +func bindReleaseOracle(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ReleaseOracleABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor), 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 (_ReleaseOracle *ReleaseOracleRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _ReleaseOracle.Contract.ReleaseOracleCaller.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 (_ReleaseOracle *ReleaseOracleRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ReleaseOracle.Contract.ReleaseOracleTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ReleaseOracle *ReleaseOracleRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ReleaseOracle.Contract.ReleaseOracleTransactor.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 (_ReleaseOracle *ReleaseOracleCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _ReleaseOracle.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 (_ReleaseOracle *ReleaseOracleTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ReleaseOracle.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ReleaseOracle *ReleaseOracleTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ReleaseOracle.Contract.contract.Transact(opts, method, params...) +} + +// AuthProposals is a free data retrieval call binding the contract method 0x282fe4e5. +// +// Solidity: function AuthProposals() constant returns(address[]) +func (_ReleaseOracle *ReleaseOracleCaller) AuthProposals(opts *bind.CallOpts) ([]common.Address, error) { + var ( + ret0 = new([]common.Address) + ) + out := ret0 + err := _ReleaseOracle.contract.Call(opts, out, "AuthProposals") + return *ret0, err +} + +// AuthProposals is a free data retrieval call binding the contract method 0x282fe4e5. +// +// Solidity: function AuthProposals() constant returns(address[]) +func (_ReleaseOracle *ReleaseOracleSession) AuthProposals() ([]common.Address, error) { + return _ReleaseOracle.Contract.AuthProposals(&_ReleaseOracle.CallOpts) +} + +// AuthProposals is a free data retrieval call binding the contract method 0x282fe4e5. +// +// Solidity: function AuthProposals() constant returns(address[]) +func (_ReleaseOracle *ReleaseOracleCallerSession) AuthProposals() ([]common.Address, error) { + return _ReleaseOracle.Contract.AuthProposals(&_ReleaseOracle.CallOpts) +} + +// AuthVotes is a free data retrieval call binding the contract method 0xa29226f2. +// +// Solidity: function AuthVotes(user address) constant returns(promote address[], demote address[]) +func (_ReleaseOracle *ReleaseOracleCaller) AuthVotes(opts *bind.CallOpts, user common.Address) (struct { + Promote []common.Address + Demote []common.Address +}, error) { + ret := new(struct { + Promote []common.Address + Demote []common.Address + }) + out := ret + err := _ReleaseOracle.contract.Call(opts, out, "AuthVotes", user) + return *ret, err +} + +// AuthVotes is a free data retrieval call binding the contract method 0xa29226f2. +// +// Solidity: function AuthVotes(user address) constant returns(promote address[], demote address[]) +func (_ReleaseOracle *ReleaseOracleSession) AuthVotes(user common.Address) (struct { + Promote []common.Address + Demote []common.Address +}, error) { + return _ReleaseOracle.Contract.AuthVotes(&_ReleaseOracle.CallOpts, user) +} + +// AuthVotes is a free data retrieval call binding the contract method 0xa29226f2. +// +// Solidity: function AuthVotes(user address) constant returns(promote address[], demote address[]) +func (_ReleaseOracle *ReleaseOracleCallerSession) AuthVotes(user common.Address) (struct { + Promote []common.Address + Demote []common.Address +}, error) { + return _ReleaseOracle.Contract.AuthVotes(&_ReleaseOracle.CallOpts, user) +} + +// CurrentVersion is a free data retrieval call binding the contract method 0x2b225f29. +// +// Solidity: function CurrentVersion() constant returns(major uint32, minor uint32, patch uint32, commit bytes20, time uint256) +func (_ReleaseOracle *ReleaseOracleCaller) CurrentVersion(opts *bind.CallOpts) (struct { + Major uint32 + Minor uint32 + Patch uint32 + Commit [20]byte + Time *big.Int +}, error) { + ret := new(struct { + Major uint32 + Minor uint32 + Patch uint32 + Commit [20]byte + Time *big.Int + }) + out := ret + err := _ReleaseOracle.contract.Call(opts, out, "CurrentVersion") + return *ret, err +} + +// CurrentVersion is a free data retrieval call binding the contract method 0x2b225f29. +// +// Solidity: function CurrentVersion() constant returns(major uint32, minor uint32, patch uint32, commit bytes20, time uint256) +func (_ReleaseOracle *ReleaseOracleSession) CurrentVersion() (struct { + Major uint32 + Minor uint32 + Patch uint32 + Commit [20]byte + Time *big.Int +}, error) { + return _ReleaseOracle.Contract.CurrentVersion(&_ReleaseOracle.CallOpts) +} + +// CurrentVersion is a free data retrieval call binding the contract method 0x2b225f29. +// +// Solidity: function CurrentVersion() constant returns(major uint32, minor uint32, patch uint32, commit bytes20, time uint256) +func (_ReleaseOracle *ReleaseOracleCallerSession) CurrentVersion() (struct { + Major uint32 + Minor uint32 + Patch uint32 + Commit [20]byte + Time *big.Int +}, error) { + return _ReleaseOracle.Contract.CurrentVersion(&_ReleaseOracle.CallOpts) +} + +// ProposedVersion is a free data retrieval call binding the contract method 0x4c327071. +// +// Solidity: function ProposedVersion() constant returns(major uint32, minor uint32, patch uint32, commit bytes20, pass address[], fail address[]) +func (_ReleaseOracle *ReleaseOracleCaller) ProposedVersion(opts *bind.CallOpts) (struct { + Major uint32 + Minor uint32 + Patch uint32 + Commit [20]byte + Pass []common.Address + Fail []common.Address +}, error) { + ret := new(struct { + Major uint32 + Minor uint32 + Patch uint32 + Commit [20]byte + Pass []common.Address + Fail []common.Address + }) + out := ret + err := _ReleaseOracle.contract.Call(opts, out, "ProposedVersion") + return *ret, err +} + +// ProposedVersion is a free data retrieval call binding the contract method 0x4c327071. +// +// Solidity: function ProposedVersion() constant returns(major uint32, minor uint32, patch uint32, commit bytes20, pass address[], fail address[]) +func (_ReleaseOracle *ReleaseOracleSession) ProposedVersion() (struct { + Major uint32 + Minor uint32 + Patch uint32 + Commit [20]byte + Pass []common.Address + Fail []common.Address +}, error) { + return _ReleaseOracle.Contract.ProposedVersion(&_ReleaseOracle.CallOpts) +} + +// ProposedVersion is a free data retrieval call binding the contract method 0x4c327071. +// +// Solidity: function ProposedVersion() constant returns(major uint32, minor uint32, patch uint32, commit bytes20, pass address[], fail address[]) +func (_ReleaseOracle *ReleaseOracleCallerSession) ProposedVersion() (struct { + Major uint32 + Minor uint32 + Patch uint32 + Commit [20]byte + Pass []common.Address + Fail []common.Address +}, error) { + return _ReleaseOracle.Contract.ProposedVersion(&_ReleaseOracle.CallOpts) +} + +// Signers is a free data retrieval call binding the contract method 0xf04c4758. +// +// Solidity: function Signers() constant returns(address[]) +func (_ReleaseOracle *ReleaseOracleCaller) Signers(opts *bind.CallOpts) ([]common.Address, error) { + var ( + ret0 = new([]common.Address) + ) + out := ret0 + err := _ReleaseOracle.contract.Call(opts, out, "Signers") + return *ret0, err +} + +// Signers is a free data retrieval call binding the contract method 0xf04c4758. +// +// Solidity: function Signers() constant returns(address[]) +func (_ReleaseOracle *ReleaseOracleSession) Signers() ([]common.Address, error) { + return _ReleaseOracle.Contract.Signers(&_ReleaseOracle.CallOpts) +} + +// Signers is a free data retrieval call binding the contract method 0xf04c4758. +// +// Solidity: function Signers() constant returns(address[]) +func (_ReleaseOracle *ReleaseOracleCallerSession) Signers() ([]common.Address, error) { + return _ReleaseOracle.Contract.Signers(&_ReleaseOracle.CallOpts) +} + +// Demote is a paid mutator transaction binding the contract method 0x80bbbd4a. +// +// Solidity: function Demote(user address) returns() +func (_ReleaseOracle *ReleaseOracleTransactor) Demote(opts *bind.TransactOpts, user common.Address) (*types.Transaction, error) { + return _ReleaseOracle.contract.Transact(opts, "Demote", user) +} + +// Demote is a paid mutator transaction binding the contract method 0x80bbbd4a. +// +// Solidity: function Demote(user address) returns() +func (_ReleaseOracle *ReleaseOracleSession) Demote(user common.Address) (*types.Transaction, error) { + return _ReleaseOracle.Contract.Demote(&_ReleaseOracle.TransactOpts, user) +} + +// Demote is a paid mutator transaction binding the contract method 0x80bbbd4a. +// +// Solidity: function Demote(user address) returns() +func (_ReleaseOracle *ReleaseOracleTransactorSession) Demote(user common.Address) (*types.Transaction, error) { + return _ReleaseOracle.Contract.Demote(&_ReleaseOracle.TransactOpts, user) +} + +// Nuke is a paid mutator transaction binding the contract method 0x0443b1ad. +// +// Solidity: function Nuke() returns() +func (_ReleaseOracle *ReleaseOracleTransactor) Nuke(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ReleaseOracle.contract.Transact(opts, "Nuke") +} + +// Nuke is a paid mutator transaction binding the contract method 0x0443b1ad. +// +// Solidity: function Nuke() returns() +func (_ReleaseOracle *ReleaseOracleSession) Nuke() (*types.Transaction, error) { + return _ReleaseOracle.Contract.Nuke(&_ReleaseOracle.TransactOpts) +} + +// Nuke is a paid mutator transaction binding the contract method 0x0443b1ad. +// +// Solidity: function Nuke() returns() +func (_ReleaseOracle *ReleaseOracleTransactorSession) Nuke() (*types.Transaction, error) { + return _ReleaseOracle.Contract.Nuke(&_ReleaseOracle.TransactOpts) +} + +// Promote is a paid mutator transaction binding the contract method 0x6195db9c. +// +// Solidity: function Promote(user address) returns() +func (_ReleaseOracle *ReleaseOracleTransactor) Promote(opts *bind.TransactOpts, user common.Address) (*types.Transaction, error) { + return _ReleaseOracle.contract.Transact(opts, "Promote", user) +} + +// Promote is a paid mutator transaction binding the contract method 0x6195db9c. +// +// Solidity: function Promote(user address) returns() +func (_ReleaseOracle *ReleaseOracleSession) Promote(user common.Address) (*types.Transaction, error) { + return _ReleaseOracle.Contract.Promote(&_ReleaseOracle.TransactOpts, user) +} + +// Promote is a paid mutator transaction binding the contract method 0x6195db9c. +// +// Solidity: function Promote(user address) returns() +func (_ReleaseOracle *ReleaseOracleTransactorSession) Promote(user common.Address) (*types.Transaction, error) { + return _ReleaseOracle.Contract.Promote(&_ReleaseOracle.TransactOpts, user) +} + +// Release is a paid mutator transaction binding the contract method 0x0d618178. +// +// Solidity: function Release(major uint32, minor uint32, patch uint32, commit bytes20) returns() +func (_ReleaseOracle *ReleaseOracleTransactor) Release(opts *bind.TransactOpts, major uint32, minor uint32, patch uint32, commit [20]byte) (*types.Transaction, error) { + return _ReleaseOracle.contract.Transact(opts, "Release", major, minor, patch, commit) +} + +// Release is a paid mutator transaction binding the contract method 0x0d618178. +// +// Solidity: function Release(major uint32, minor uint32, patch uint32, commit bytes20) returns() +func (_ReleaseOracle *ReleaseOracleSession) Release(major uint32, minor uint32, patch uint32, commit [20]byte) (*types.Transaction, error) { + return _ReleaseOracle.Contract.Release(&_ReleaseOracle.TransactOpts, major, minor, patch, commit) +} + +// Release is a paid mutator transaction binding the contract method 0x0d618178. +// +// Solidity: function Release(major uint32, minor uint32, patch uint32, commit bytes20) returns() +func (_ReleaseOracle *ReleaseOracleTransactorSession) Release(major uint32, minor uint32, patch uint32, commit [20]byte) (*types.Transaction, error) { + return _ReleaseOracle.Contract.Release(&_ReleaseOracle.TransactOpts, major, minor, patch, commit) +} + +// UpdateRelease is a paid mutator transaction binding the contract method 0x645dce72. +// +// Solidity: function updateRelease(major uint32, minor uint32, patch uint32, commit bytes20, release bool) returns() +func (_ReleaseOracle *ReleaseOracleTransactor) UpdateRelease(opts *bind.TransactOpts, major uint32, minor uint32, patch uint32, commit [20]byte, release bool) (*types.Transaction, error) { + return _ReleaseOracle.contract.Transact(opts, "updateRelease", major, minor, patch, commit, release) +} + +// UpdateRelease is a paid mutator transaction binding the contract method 0x645dce72. +// +// Solidity: function updateRelease(major uint32, minor uint32, patch uint32, commit bytes20, release bool) returns() +func (_ReleaseOracle *ReleaseOracleSession) UpdateRelease(major uint32, minor uint32, patch uint32, commit [20]byte, release bool) (*types.Transaction, error) { + return _ReleaseOracle.Contract.UpdateRelease(&_ReleaseOracle.TransactOpts, major, minor, patch, commit, release) +} + +// UpdateRelease is a paid mutator transaction binding the contract method 0x645dce72. +// +// Solidity: function updateRelease(major uint32, minor uint32, patch uint32, commit bytes20, release bool) returns() +func (_ReleaseOracle *ReleaseOracleTransactorSession) UpdateRelease(major uint32, minor uint32, patch uint32, commit [20]byte, release bool) (*types.Transaction, error) { + return _ReleaseOracle.Contract.UpdateRelease(&_ReleaseOracle.TransactOpts, major, minor, patch, commit, release) +} + +// UpdateSigner is a paid mutator transaction binding the contract method 0xf460590b. +// +// Solidity: function updateSigner(user address, authorize bool) returns() +func (_ReleaseOracle *ReleaseOracleTransactor) UpdateSigner(opts *bind.TransactOpts, user common.Address, authorize bool) (*types.Transaction, error) { + return _ReleaseOracle.contract.Transact(opts, "updateSigner", user, authorize) +} + +// UpdateSigner is a paid mutator transaction binding the contract method 0xf460590b. +// +// Solidity: function updateSigner(user address, authorize bool) returns() +func (_ReleaseOracle *ReleaseOracleSession) UpdateSigner(user common.Address, authorize bool) (*types.Transaction, error) { + return _ReleaseOracle.Contract.UpdateSigner(&_ReleaseOracle.TransactOpts, user, authorize) +} + +// UpdateSigner is a paid mutator transaction binding the contract method 0xf460590b. +// +// Solidity: function updateSigner(user address, authorize bool) returns() +func (_ReleaseOracle *ReleaseOracleTransactorSession) UpdateSigner(user common.Address, authorize bool) (*types.Transaction, error) { + return _ReleaseOracle.Contract.UpdateSigner(&_ReleaseOracle.TransactOpts, user, authorize) +} diff --git a/contracts/release/contract.sol b/contracts/release/contract.sol new file mode 100644 index 000000000..7ec0fa8e7 --- /dev/null +++ b/contracts/release/contract.sol @@ -0,0 +1,240 @@ +// 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 <http://www.gnu.org/licenses/>. + +// ReleaseOracle is an Ethereum contract to store the current and previous +// versions of the go-ethereum implementation. Its goal is to allow Geth to +// check for new releases automatically without the need to consult a central +// repository. +// +// The contract takes a vote based approach on both assigning authorized signers +// as well as signing off on new Geth releases. +// +// Note, when a signer is demoted, the currently pending release is auto-nuked. +// The reason is to prevent suprises where a demotion actually tilts the votes +// in favor of one voter party and pushing out a new release as a consequence of +// a simple demotion. +contract ReleaseOracle { + // Votes is an internal data structure to count votes on a specific proposal + struct Votes { + address[] pass; // List of signers voting to pass a proposal + address[] fail; // List of signers voting to fail a proposal + } + + // Version is the version details of a particular Geth release + struct Version { + uint32 major; // Major version component of the release + uint32 minor; // Minor version component of the release + uint32 patch; // Patch version component of the release + bytes20 commit; // Git SHA1 commit hash of the release + + uint64 time; // Timestamp of the release approval + Votes votes; // Votes that passed this release + } + + // Oracle authorization details + mapping(address => bool) authorized; // Set of accounts allowed to vote on updating the contract + address[] signers; // List of addresses currently accepted as signers + + // Various proposals being voted on + mapping(address => Votes) authProps; // Currently running user authorization proposals + address[] authPend; // List of addresses being voted on (map indexes) + + Version verProp; // Currently proposed release being voted on + Version[] releases; // All the positively voted releases + + // isSigner is a modifier to authorize contract transactions. + modifier isSigner() { + if (authorized[msg.sender]) { + _ + } + } + + // Constructor to assign the creator as the sole valid signer. + function ReleaseOracle() { + authorized[msg.sender] = true; + signers.push(msg.sender); + } + + // Signers is an accessor method to retrieve all te signers (public accessor + // generates an indexed one, not a retreive-all version). + function Signers() constant returns(address[]) { + return signers; + } + + // AuthProposals retrieves the list of addresses that authorization proposals + // are currently being voted on. + function AuthProposals() constant returns(address[]) { + return authPend; + } + + // AuthVotes retrieves the current authorization votes for a particular user + // to promote him into the list of signers, or demote him from there. + function AuthVotes(address user) constant returns(address[] promote, address[] demote) { + return (authProps[user].pass, authProps[user].fail); + } + + // CurrentVersion retrieves the semantic version, commit hash and release time + // of the currently votec active release. + function CurrentVersion() constant returns (uint32 major, uint32 minor, uint32 patch, bytes20 commit, uint time) { + if (releases.length == 0) { + return (0, 0, 0, 0, 0); + } + var release = releases[releases.length - 1]; + + return (release.major, release.minor, release.patch, release.commit, release.time); + } + + // ProposedVersion retrieves the semantic version, commit hash and the current + // votes for the next proposed release. + function ProposedVersion() constant returns (uint32 major, uint32 minor, uint32 patch, bytes20 commit, address[] pass, address[] fail) { + return (verProp.major, verProp.minor, verProp.patch, verProp.commit, verProp.votes.pass, verProp.votes.fail); + } + + // Promote pitches in on a voting campaign to promote a new user to a signer + // position. + function Promote(address user) { + updateSigner(user, true); + } + + // Demote pitches in on a voting campaign to demote an authorized user from + // its signer position. + function Demote(address user) { + updateSigner(user, false); + } + + // Release votes for a particular version to be included as the next release. + function Release(uint32 major, uint32 minor, uint32 patch, bytes20 commit) { + updateRelease(major, minor, patch, commit, true); + } + + // Nuke votes for the currently proposed version to not be included as the next + // release. Nuking doesn't require a specific version number for simplicity. + function Nuke() { + updateRelease(0, 0, 0, 0, false); + } + + // updateSigner marks a vote for changing the status of an Ethereum user, either + // for or against the user being an authorized signer. + function updateSigner(address user, bool authorize) isSigner { + // Gather the current votes and ensure we don't double vote + Votes votes = authProps[user]; + for (uint i = 0; i < votes.pass.length; i++) { + if (votes.pass[i] == msg.sender) { + return; + } + } + for (i = 0; i < votes.fail.length; i++) { + if (votes.fail[i] == msg.sender) { + return; + } + } + // If no authorization proposal is open, add the user to the index for later lookups + if (votes.pass.length == 0 && votes.fail.length == 0) { + authPend.push(user); + } + // Cast the vote and return if the proposal cannot be resolved yet + if (authorize) { + votes.pass.push(msg.sender); + if (votes.pass.length <= signers.length / 2) { + return; + } + } else { + votes.fail.push(msg.sender); + if (votes.fail.length <= signers.length / 2) { + return; + } + } + // Proposal resolved in our favor, execute whatever we voted on + if (authorize && !authorized[user]) { + authorized[user] = true; + signers.push(user); + } else if (!authorize && authorized[user]) { + authorized[user] = false; + + for (i = 0; i < signers.length; i++) { + if (signers[i] == user) { + signers[i] = signers[signers.length - 1]; + signers.length--; + + delete verProp; // Nuke any version proposal (no suprise releases!) + break; + } + } + } + // Finally delete the resolved proposal, index and garbage collect + delete authProps[user]; + + for (i = 0; i < authPend.length; i++) { + if (authPend[i] == user) { + authPend[i] = authPend[authPend.length - 1]; + authPend.length--; + break; + } + } + } + + // updateRelease votes for a particular version to be included as the next release, + // or for the currently proposed release to be nuked out. + function updateRelease(uint32 major, uint32 minor, uint32 patch, bytes20 commit, bool release) isSigner { + // Skip nuke votes if no proposal is pending + if (!release && verProp.votes.pass.length == 0) { + return; + } + // Mark a new release if no proposal is pending + if (verProp.votes.pass.length == 0) { + verProp.major = major; + verProp.minor = minor; + verProp.patch = patch; + verProp.commit = commit; + } + // Make sure positive votes match the current proposal + if (release && (verProp.major != major || verProp.minor != minor || verProp.patch != patch || verProp.commit != commit)) { + return; + } + // Gather the current votes and ensure we don't double vote + Votes votes = verProp.votes; + for (uint i = 0; i < votes.pass.length; i++) { + if (votes.pass[i] == msg.sender) { + return; + } + } + for (i = 0; i < votes.fail.length; i++) { + if (votes.fail[i] == msg.sender) { + return; + } + } + // Cast the vote and return if the proposal cannot be resolved yet + if (release) { + votes.pass.push(msg.sender); + if (votes.pass.length <= signers.length / 2) { + return; + } + } else { + votes.fail.push(msg.sender); + if (votes.fail.length <= signers.length / 2) { + return; + } + } + // Proposal resolved in our favor, execute whatever we voted on + if (release) { + verProp.time = uint64(now); + releases.push(verProp); + delete verProp; + } else { + delete verProp; + } + } +} diff --git a/contracts/release/contract_test.go b/contracts/release/contract_test.go new file mode 100644 index 000000000..7aa3a577d --- /dev/null +++ b/contracts/release/contract_test.go @@ -0,0 +1,374 @@ +// 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 <http://www.gnu.org/licenses/>. + +package release + +import ( + "crypto/ecdsa" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" +) + +// setupReleaseTest creates a blockchain simulator and deploys a version oracle +// contract for testing. +func setupReleaseTest(t *testing.T, prefund ...*ecdsa.PrivateKey) (*ecdsa.PrivateKey, *ReleaseOracle, *backends.SimulatedBackend) { + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth := bind.NewKeyedTransactor(key) + + accounts := []core.GenesisAccount{{Address: auth.From, Balance: big.NewInt(10000000000)}} + for _, key := range prefund { + accounts = append(accounts, core.GenesisAccount{Address: crypto.PubkeyToAddress(key.PublicKey), Balance: big.NewInt(10000000000)}) + } + sim := backends.NewSimulatedBackend(accounts...) + + // Deploy a version oracle contract, commit and return + _, _, oracle, err := DeployReleaseOracle(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy version contract: %v", err) + } + sim.Commit() + + return key, oracle, sim +} + +// Tests that the version contract can be deployed and the creator is assigned +// the sole authorized signer. +func TestContractCreation(t *testing.T) { + key, oracle, _ := setupReleaseTest(t) + + owner := crypto.PubkeyToAddress(key.PublicKey) + signers, err := oracle.Signers(nil) + if err != nil { + t.Fatalf("Failed to retrieve list of signers: %v", err) + } + if len(signers) != 1 || signers[0] != owner { + t.Fatalf("Initial signer mismatch: have %v, want %v", signers, owner) + } +} + +// Tests that subsequent signers can be promoted, each requiring half plus one +// votes for it to pass through. +func TestSignerPromotion(t *testing.T) { + // Prefund a few accounts to authorize with and create the oracle + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + } + key, oracle, sim := setupReleaseTest(t, keys...) + + // Gradually promote the keys, until all are authorized + keys = append([]*ecdsa.PrivateKey{key}, keys...) + for i := 1; i < len(keys); i++ { + // Check that no votes are accepted from the not yet authed user + if _, err := oracle.Promote(bind.NewKeyedTransactor(keys[i]), common.Address{}); err != nil { + t.Fatalf("Iter #%d: failed invalid promotion attempt: %v", i, err) + } + sim.Commit() + + pend, err := oracle.AuthProposals(nil) + if err != nil { + t.Fatalf("Iter #%d: failed to retrieve active proposals: %v", i, err) + } + if len(pend) != 0 { + t.Fatalf("Iter #%d: proposal count mismatch: have %d, want 0", i, len(pend)) + } + // Promote with half - 1 voters and check that the user's not yet authorized + for j := 0; j < i/2; j++ { + if _, err = oracle.Promote(bind.NewKeyedTransactor(keys[j]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil { + t.Fatalf("Iter #%d: failed valid promotion attempt: %v", i, err) + } + } + sim.Commit() + + signers, err := oracle.Signers(nil) + if err != nil { + t.Fatalf("Iter #%d: failed to retrieve list of signers: %v", i, err) + } + if len(signers) != i { + t.Fatalf("Iter #%d: signer count mismatch: have %v, want %v", i, len(signers), i) + } + // Promote with the last one needed to pass the promotion + if _, err = oracle.Promote(bind.NewKeyedTransactor(keys[i/2]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil { + t.Fatalf("Iter #%d: failed valid promotion completion attempt: %v", i, err) + } + sim.Commit() + + signers, err = oracle.Signers(nil) + if err != nil { + t.Fatalf("Iter #%d: failed to retrieve list of signers: %v", i, err) + } + if len(signers) != i+1 { + t.Fatalf("Iter #%d: signer count mismatch: have %v, want %v", i, len(signers), i+1) + } + } +} + +// Tests that subsequent signers can be demoted, each requiring half plus one +// votes for it to pass through. +func TestSignerDemotion(t *testing.T) { + // Prefund a few accounts to authorize with and create the oracle + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + } + key, oracle, sim := setupReleaseTest(t, keys...) + + // Authorize all the keys as valid signers and verify cardinality + keys = append([]*ecdsa.PrivateKey{key}, keys...) + for i := 1; i < len(keys); i++ { + for j := 0; j <= i/2; j++ { + if _, err := oracle.Promote(bind.NewKeyedTransactor(keys[j]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil { + t.Fatalf("Iter #%d: failed valid promotion attempt: %v", i, err) + } + } + sim.Commit() + } + signers, err := oracle.Signers(nil) + if err != nil { + t.Fatalf("Failed to retrieve list of signers: %v", err) + } + if len(signers) != len(keys) { + t.Fatalf("Signer count mismatch: have %v, want %v", len(signers), len(keys)) + } + // Gradually demote users until we run out of signers + for i := len(keys) - 1; i >= 0; i-- { + // Demote with half - 1 voters and check that the user's not yet dropped + for j := 0; j < (i+1)/2; j++ { + if _, err = oracle.Demote(bind.NewKeyedTransactor(keys[j]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil { + t.Fatalf("Iter #%d: failed valid demotion attempt: %v", len(keys)-i, err) + } + } + sim.Commit() + + signers, err := oracle.Signers(nil) + if err != nil { + t.Fatalf("Iter #%d: failed to retrieve list of signers: %v", len(keys)-i, err) + } + if len(signers) != i+1 { + t.Fatalf("Iter #%d: signer count mismatch: have %v, want %v", len(keys)-i, len(signers), i+1) + } + // Demote with the last one needed to pass the demotion + if _, err = oracle.Demote(bind.NewKeyedTransactor(keys[(i+1)/2]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil { + t.Fatalf("Iter #%d: failed valid demotion completion attempt: %v", i, err) + } + sim.Commit() + + signers, err = oracle.Signers(nil) + if err != nil { + t.Fatalf("Iter #%d: failed to retrieve list of signers: %v", len(keys)-i, err) + } + if len(signers) != i { + t.Fatalf("Iter #%d: signer count mismatch: have %v, want %v", len(keys)-i, len(signers), i) + } + // Check that no votes are accepted from the already demoted users + if _, err = oracle.Promote(bind.NewKeyedTransactor(keys[i]), common.Address{}); err != nil { + t.Fatalf("Iter #%d: failed invalid promotion attempt: %v", i, err) + } + sim.Commit() + + pend, err := oracle.AuthProposals(nil) + if err != nil { + t.Fatalf("Iter #%d: failed to retrieve active proposals: %v", i, err) + } + if len(pend) != 0 { + t.Fatalf("Iter #%d: proposal count mismatch: have %d, want 0", i, len(pend)) + } + } +} + +// Tests that new versions can be released, honouring both voting rights as well +// as the minimum required vote count. +func TestVersionRelease(t *testing.T) { + // Prefund a few accounts to authorize with and create the oracle + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + } + key, oracle, sim := setupReleaseTest(t, keys...) + + // Track the "current release" + var ( + verMajor = uint32(0) + verMinor = uint32(0) + verPatch = uint32(0) + verCommit = [20]byte{} + ) + // Gradually push releases, always requiring more signers than previously + keys = append([]*ecdsa.PrivateKey{key}, keys...) + for i := 1; i < len(keys); i++ { + // Check that no votes are accepted from the not yet authed user + if _, err := oracle.Release(bind.NewKeyedTransactor(keys[i]), 0, 0, 0, [20]byte{0}); err != nil { + t.Fatalf("Iter #%d: failed invalid release attempt: %v", i, err) + } + sim.Commit() + + prop, err := oracle.ProposedVersion(nil) + if err != nil { + t.Fatalf("Iter #%d: failed to retrieve active proposal: %v", i, err) + } + if len(prop.Pass) != 0 { + t.Fatalf("Iter #%d: proposal vote count mismatch: have %d, want 0", i, len(prop.Pass)) + } + // Authorize the user to make releases + for j := 0; j <= i/2; j++ { + if _, err = oracle.Promote(bind.NewKeyedTransactor(keys[j]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil { + t.Fatalf("Iter #%d: failed valid promotion attempt: %v", i, err) + } + } + sim.Commit() + + // Propose release with half voters and check that the release does not yet go through + for j := 0; j < (i+1)/2; j++ { + if _, err = oracle.Release(bind.NewKeyedTransactor(keys[j]), uint32(i), uint32(i+1), uint32(i+2), [20]byte{}); err != nil { + t.Fatalf("Iter #%d: failed valid release attempt: %v", i, err) + } + } + sim.Commit() + + ver, err := oracle.CurrentVersion(nil) + if err != nil { + t.Fatalf("Iter #%d: failed to retrieve current version: %v", i, err) + } + if ver.Major != verMajor || ver.Minor != verMinor || ver.Patch != verPatch || ver.Commit != verCommit { + t.Fatalf("Iter #%d: version mismatch: have %d.%d.%d-%x, want %d.%d.%d-%x", i, ver.Major, ver.Minor, ver.Patch, ver.Commit, verMajor, verMinor, verPatch, verCommit) + } + + // Pass the release and check that it became the next version + verMajor, verMinor, verPatch, verCommit = uint32(i), uint32(i+1), uint32(i+2), [20]byte{} + if _, err = oracle.Release(bind.NewKeyedTransactor(keys[(i+1)/2]), uint32(i), uint32(i+1), uint32(i+2), [20]byte{}); err != nil { + t.Fatalf("Iter #%d: failed valid release completion attempt: %v", i, err) + } + sim.Commit() + + ver, err = oracle.CurrentVersion(nil) + if err != nil { + t.Fatalf("Iter #%d: failed to retrieve current version: %v", i, err) + } + if ver.Major != verMajor || ver.Minor != verMinor || ver.Patch != verPatch || ver.Commit != verCommit { + t.Fatalf("Iter #%d: version mismatch: have %d.%d.%d-%x, want %d.%d.%d-%x", i, ver.Major, ver.Minor, ver.Patch, ver.Commit, verMajor, verMinor, verPatch, verCommit) + } + } +} + +// Tests that proposed versions can be nuked out of existence. +func TestVersionNuking(t *testing.T) { + // Prefund a few accounts to authorize with and create the oracle + keys := make([]*ecdsa.PrivateKey, 9) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + } + key, oracle, sim := setupReleaseTest(t, keys...) + + // Authorize all the keys as valid signers + keys = append([]*ecdsa.PrivateKey{key}, keys...) + for i := 1; i < len(keys); i++ { + for j := 0; j <= i/2; j++ { + if _, err := oracle.Promote(bind.NewKeyedTransactor(keys[j]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil { + t.Fatalf("Iter #%d: failed valid promotion attempt: %v", i, err) + } + } + sim.Commit() + } + // Propose releases with more and more keys, always retaining enough users to nuke the proposals + for i := 1; i < (len(keys)+1)/2; i++ { + // Propose release with an initial set of signers + for j := 0; j < i; j++ { + if _, err := oracle.Release(bind.NewKeyedTransactor(keys[j]), uint32(i), uint32(i+1), uint32(i+2), [20]byte{}); err != nil { + t.Fatalf("Iter #%d: failed valid proposal attempt: %v", i, err) + } + } + sim.Commit() + + prop, err := oracle.ProposedVersion(nil) + if err != nil { + t.Fatalf("Iter #%d: failed to retrieve active proposal: %v", i, err) + } + if len(prop.Pass) != i { + t.Fatalf("Iter #%d: proposal vote count mismatch: have %d, want %d", i, len(prop.Pass), i) + } + // Nuke the release with half+1 voters + for j := i; j <= i+(len(keys)+1)/2; j++ { + if _, err := oracle.Nuke(bind.NewKeyedTransactor(keys[j])); err != nil { + t.Fatalf("Iter #%d: failed valid nuke attempt: %v", i, err) + } + } + sim.Commit() + + prop, err = oracle.ProposedVersion(nil) + if err != nil { + t.Fatalf("Iter #%d: failed to retrieve active proposal: %v", i, err) + } + if len(prop.Pass) != 0 || len(prop.Fail) != 0 { + t.Fatalf("Iter #%d: proposal vote count mismatch: have %d/%d pass/fail, want 0/0", i, len(prop.Pass), len(prop.Fail)) + } + } +} + +// Tests that demoting a signer will auto-nuke the currently pending release. +func TestVersionAutoNuke(t *testing.T) { + // Prefund a few accounts to authorize with and create the oracle + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + } + key, oracle, sim := setupReleaseTest(t, keys...) + + // Authorize all the keys as valid signers + keys = append([]*ecdsa.PrivateKey{key}, keys...) + for i := 1; i < len(keys); i++ { + for j := 0; j <= i/2; j++ { + if _, err := oracle.Promote(bind.NewKeyedTransactor(keys[j]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil { + t.Fatalf("Iter #%d: failed valid promotion attempt: %v", i, err) + } + } + sim.Commit() + } + // Make a release proposal and check it's existence + if _, err := oracle.Release(bind.NewKeyedTransactor(keys[0]), 1, 2, 3, [20]byte{}); err != nil { + t.Fatalf("Failed valid proposal attempt: %v", err) + } + sim.Commit() + + prop, err := oracle.ProposedVersion(nil) + if err != nil { + t.Fatalf("Failed to retrieve active proposal: %v", err) + } + if len(prop.Pass) != 1 { + t.Fatalf("Proposal vote count mismatch: have %d, want 1", len(prop.Pass)) + } + // Demote a signer and check release proposal deletion + for i := 0; i <= len(keys)/2; i++ { + if _, err := oracle.Demote(bind.NewKeyedTransactor(keys[i]), crypto.PubkeyToAddress(keys[len(keys)-1].PublicKey)); err != nil { + t.Fatalf("Iter #%d: failed valid demotion attempt: %v", i, err) + } + } + sim.Commit() + + prop, err = oracle.ProposedVersion(nil) + if err != nil { + t.Fatalf("Failed to retrieve active proposal: %v", err) + } + if len(prop.Pass) != 0 { + t.Fatalf("Proposal vote count mismatch: have %d, want 0", len(prop.Pass)) + } +} diff --git a/contracts/release/generator.go b/contracts/release/generator.go new file mode 100644 index 000000000..1553e0612 --- /dev/null +++ b/contracts/release/generator.go @@ -0,0 +1,19 @@ +// 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 <http://www.gnu.org/licenses/>. + +//go:generate abigen --sol ./contract.sol --pkg release --out ./contract.go + +package release |