// 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 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, []common.Address{auth.From}) 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{byte(i + 3)}); 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{byte(i + 3)} if _, err = oracle.Release(bind.NewKeyedTransactor(keys[(i+1)/2]), uint32(i), uint32(i+1), uint32(i+2), [20]byte{byte(i + 3)}); 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{byte(i + 3)}); 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{4}); 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)) } }