0

Im working on refactoring clique consensus algorithm to use a smart contract.

For my initial proof of concept I am using 2 files, each with 3 node addresses, I am using these files to populate the authorized signers array..

    func getSignersFromFile() ([]string, []string) {

        var group1 = make([]string, 0)
        var group2 = make([]string, 0)

        group1File, err := os.Open("/signers-group-1.txt")
        if err != nil {
            log.Fatal(err)
        }
        defer group1File.Close()

        group2File, err := os.Open("/signers-group-2.txt")
        if err != nil {
            log.Fatal(err)
        }
        defer group2File.Close()

        scanner1 := bufio.NewScanner(group1File)

        for scanner1.Scan() {
            group1 = append(group1, scanner1.Text())
        }
        if err := scanner1.Err(); err != nil {
            log.Fatal(err)
        }

        scanner2 := bufio.NewScanner(group2File)
        for scanner2.Scan() {
            group2 = append(group2, scanner2.Text())
        }
        if err := scanner2.Err(); err != nil {
            log.Fatal(err)
        }

        return group1, group2

    }

then my main "Consensus" function gets these 2 groups and intermittently switches between each group of signers depending on what numeric parameter the function receives (i.e 1 = group 1 and 2 = group 2).

    func (c *Clique) switchingSigners(switcher uint32, snap *Snapshot) {

        group1, group2 := getSignersFromFile()

        c.lock.RLock()

        formattedSignersG1 := make([]common.Address, 0, len(group1))
        formattedSignersG2 := make([]common.Address, 0, len(group2))

        // pass each group address string values to the conversion function
        for _, address1 := range group1 {
            formattedSignersG1 = append(formattedSignersG1, convertToAddress(address1))
        }
        for _, address2 := range group2 {
            formattedSignersG2 = append(formattedSignersG2, convertToAddress(address2))
        }

        if switcher == 1 {
            for _, fmtAdd2 := range formattedSignersG2 {
                delete(snap.Signers, fmtAdd2)
            }
            for _, fmtAdd1 := range formattedSignersG1 {
                snap.Signers[fmtAdd1] = struct{}{}
            }
        } else {
            for _, fmtAdd1 := range formattedSignersG1 {
                delete(snap.Signers, fmtAdd1)
            }
            for _, fmtAdd2 := range formattedSignersG2 {
                snap.Signers[fmtAdd2] = struct{}{}
            }
        }

        c.lock.RUnlock()

    }

The above switchingSigners() function is placed into the Cliques Prepare() function which I have refactored to remove the header based voting logic (I also removed the voting/tally logic from the apply() function )

    func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) error {

        number := header.Number.Uint64()

        snap, err := c.snapshot(chain, number-1, header.ParentHash, nil)
        if err != nil {
            return err
        }

        if number%c.config.Epoch != 0 {

            if number > 10 {
                fmt.Println(">>>> 19 BLOCKS HIT")
                if number == 20 {
                    fmt.Println(">>> reached block 20 ")
                    c.switchingSigners(2, snap)
                }
                if number == 30 {
                    fmt.Println(">>> reached block 30 ")
                    c.switchingSigners(1, snap)
                }
            }

        }

        header.Difficulty = CalcDifficulty(snap, c.signer)

        if len(header.Extra) < extraVanity {
            header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...)
        }
        header.Extra = header.Extra[:extraVanity]

        if number%c.config.Epoch == 0 {
            for _, signer := range snap.signers() {
                header.Extra = append(header.Extra, signer[:]...)
            }
        }
        header.Extra = append(header.Extra, make([]byte, extraSeal)...)

        header.MixDigest = common.Hash{}

        parent := chain.GetHeader(header.ParentHash, number-1)
        if parent == nil {
            return consensus.ErrUnknownAncestor
        }
        header.Time = new(big.Int).Add(parent.Time, new(big.Int).SetUint64(c.config.Period))
        if header.Time.Int64() < time.Now().Unix() {
            header.Time = big.NewInt(time.Now().Unix())
        }
        return nil
    }

It seems to work fine, until I stop all of my nodes and when I restart them I get the error "Invalid Hash Chain Retrieved" or "Unauthorized".

This logic I have here is obviously flawed/rough but what I am trying to achieve is moving all the voting from clique to a smart contract that is based on the Ethereum mainnet, Has anyone got any ideas on where I am going wrong here or also any information/links/tips/hints/notes on people who have created their own consensus algorithm into Geth.

And to show all, here is the refactored apply() function:

    // apply creates a new authorization snapshot by applying the given headers to
    func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {

        if len(headers) == 0 {
            return s, nil
        }

        for i := 0; i < len(headers)-1; i++ {
            if headers[i+1].Number.Uint64() != headers[i].Number.Uint64()+1 {
                return nil, errInvalidVotingChain
            }
        }
        if headers[0].Number.Uint64() != s.Number+1 {
            return nil, errInvalidVotingChain
        }

        snap := s.copy()

        for _, header := range headers {
            // Remove any votes on checkpoint blocks
            number := header.Number.Uint64()

            // Delete the oldest signer from the recent list to allow it signing again
            if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {
                delete(snap.Recents, number-limit)
            }
            // Resolve the authorization key and check against signers
            signer, err := ecrecover(header, s.sigcache)
            if err != nil {
                return nil, err
            }

            // fmt.Println(hex.EncodeToString(signer[:]))
            if _, ok := snap.Signers[signer]; !ok {
                fmt.Println("signers !ok - not in signers array ")
                return nil, errUnauthorized
            }
            for _, recent := range snap.Recents {
                if recent == signer {
                    fmt.Println("signers unauth")
                    return nil, errUnauthorized
                }
            }
            snap.Recents[number] = signer

        }
        snap.Number += uint64(len(headers))
        snap.Hash = headers[len(headers)-1].Hash()

        return snap, nil
    }
  • Why not put a breakpoint on all instances of errInvalidChain and see where it trips? – rustyx Jul 2 '18 at 13:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.