From e342ca38ed287273fa22821e1e7a63bf59cb71f7 Mon Sep 17 00:00:00 2001 From: Vincent Geddes Date: Wed, 1 Jun 2022 13:19:46 +0200 Subject: [PATCH] Fix parsing of bitfields (#635) Co-authored-by: Alistair Singh --- relayer/relays/beefy/bitfield/bitfield.go | 43 ++++++++++++++ .../relays/beefy/bitfield/bitfield_test.go | 57 +++++++++++++++++++ relayer/relays/beefy/ethereum-writer.go | 24 ++------ relayer/relays/beefy/parameters.go | 26 ++++----- 4 files changed, 114 insertions(+), 36 deletions(-) create mode 100644 relayer/relays/beefy/bitfield/bitfield.go create mode 100644 relayer/relays/beefy/bitfield/bitfield_test.go diff --git a/relayer/relays/beefy/bitfield/bitfield.go b/relayer/relays/beefy/bitfield/bitfield.go new file mode 100644 index 0000000000000..a4b31910b7b8f --- /dev/null +++ b/relayer/relays/beefy/bitfield/bitfield.go @@ -0,0 +1,43 @@ +package bitfield + +import ( + "math/big" +) + +type Bitfield []byte + +func reverse(thing []byte) { + for i, j := 0, len(thing)-1; i < j; i, j = i+1, j-1 { + thing[i], thing[j] = thing[j], thing[i] + } +} + +// New returns a Bitfield initialized from the Solidity representation of a Bitfield (an array of uint256). See below: +// https://github.com/Snowfork/snowbridge/blob/18c6225b21782170156729d54a35404d876a2c7b/ethereum/contracts/utils/Bitfield.sol +func New(input []*big.Int) Bitfield { + const length = 256 / 8 + result := make(Bitfield, length*len(input)) + for i, chunk := range input { + k := i * length + j := (i + 1) * length + chunk.FillBytes(result[k:j]) + reverse(result[k:j]) + } + return result +} + +// Members returns the set bits in the bitfield +func (b Bitfield) Members() []uint64 { + results := []uint64{} + for idx, bits := range b { + if bits == 0 { + continue + } + for i := 0; i < 8; i++ { + if bits&byte(1< 0 { + results = append(results, uint64((idx*8)+i)) + } + } + } + return results +} diff --git a/relayer/relays/beefy/bitfield/bitfield_test.go b/relayer/relays/beefy/bitfield/bitfield_test.go new file mode 100644 index 0000000000000..4aae73fadfe16 --- /dev/null +++ b/relayer/relays/beefy/bitfield/bitfield_test.go @@ -0,0 +1,57 @@ +package bitfield + +import ( + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBitfieldMembers(t *testing.T) { + x := big.NewInt(1) + y := big.NewInt(1) + z := big.NewInt(1) + + u := New([]*big.Int{x, y, z}) + fmt.Printf("%v\n", u) + fmt.Printf("%v\n", u.Members()) + + assert.Equal(t, u.Members(), []uint64{0, 256, 512}) +} + +func TestBitfieldMembers2(t *testing.T) { + foo := make([]byte, 32) + foo[0] = 128 + foo[31] = 1 + + x := big.NewInt(1) + x.SetBytes(foo) + + u := New([]*big.Int{x}) + fmt.Printf("%v\n", u) + fmt.Printf("%v\n", u.Members()) + + assert.Equal(t, u.Members(), []uint64{0, 255}) +} + +func TestBitfiledMembers3(t *testing.T) { + var x, y, z, w big.Int + + // Four uint256 with first and last bit set + x.SetString("8000000000000000000000000000000000000000000000000000000000000001", 16) + y.SetString("8000000000000000000000000000000000000000000000000000000000000001", 16) + z.SetString("8000000000000000000000000000000000000000000000000000000000000001", 16) + w.SetString("8000000000000000000000000000000000000000000000000000000000000001", 16) + + u := New([]*big.Int{&x, &y, &z, &w}) + fmt.Printf("%v\n", u) + fmt.Printf("%v\n", u.Members()) + + assert.Equal(t, u.Members(), []uint64{ + /* x */ 0, 255, + /* y */ 256, 511, + /* z */ 512, 767, + /* w */ 768, 1023, + }) +} diff --git a/relayer/relays/beefy/ethereum-writer.go b/relayer/relays/beefy/ethereum-writer.go index 28140d3a7ff16..5967cbcae154c 100644 --- a/relayer/relays/beefy/ethereum-writer.go +++ b/relayer/relays/beefy/ethereum-writer.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "math/big" - "strconv" "time" "golang.org/x/sync/errgroup" @@ -20,6 +19,7 @@ import ( "github.com/snowfork/snowbridge/relayer/chain/ethereum" "github.com/snowfork/snowbridge/relayer/contracts/beefyclient" + "github.com/snowfork/snowbridge/relayer/relays/beefy/bitfield" log "github.com/sirupsen/logrus" ) @@ -268,25 +268,9 @@ func (wr *EthereumWriter) doSubmitInitial(ctx context.Context, task *Request) (* return tx, nil } -func bitfieldToString(bitfield []*big.Int) string { - bitfieldString := "" - for _, bitfieldInt := range bitfield { - bits := strconv.FormatInt(bitfieldInt.Int64(), 2) - - // add bits from this int at leftmost position - bitfieldString = bits + bitfieldString - - // pad to 256 bits to include missing validators - for bitsLength := len(bits); bitsLength < 256; bitsLength++ { - bitfieldString = "0" + bitfieldString - } - } - return bitfieldString -} - // doFinalSubmit sends a SubmitFinal tx to the BeefyClient contract func (wr *EthereumWriter) doSubmitFinal(ctx context.Context, validationID int64, task *Request) (*types.Transaction, error) { - randomBitfield, err := wr.contract.CreateFinalBitfield( + finalBitfield, err := wr.contract.CreateFinalBitfield( &bind.CallOpts{Pending: true}, big.NewInt(validationID), ) @@ -294,9 +278,9 @@ func (wr *EthereumWriter) doSubmitFinal(ctx context.Context, validationID int64, return nil, fmt.Errorf("create validator bitfield: %w", err) } - bitfield := bitfieldToString(randomBitfield) + validatorIndices := bitfield.New(finalBitfield).Members() - params, err := task.MakeSubmitFinalParams(validationID, bitfield) + params, err := task.MakeSubmitFinalParams(validationID, validatorIndices) if err != nil { return nil, err } diff --git a/relayer/relays/beefy/parameters.go b/relayer/relays/beefy/parameters.go index b362d7001885b..4d2cf4c57270e 100644 --- a/relayer/relays/beefy/parameters.go +++ b/relayer/relays/beefy/parameters.go @@ -94,32 +94,21 @@ func (r *Request) generateValidatorAddressProof(validatorIndex int64) ([][32]byt return proof, nil } -func (r *Request) MakeSubmitFinalParams(validationID int64, bitfield string) (*FinalRequestParams, error) { +func (r *Request) MakeSubmitFinalParams(validationID int64, validatorIndices []uint64) (*FinalRequestParams, error) { validationDataID := big.NewInt(validationID) - validatorIndices := []*big.Int{} - - // bitfield is right to left order, so loop backwards - for i := len(bitfield) - 1; i >= 0; i-- { - bit := bitfield[i : i+1] - if bit == "1" { - position := len(bitfield) - 1 - i // positions start from 0 and increase to len(bitfield) - 1 - validatorIndices = append(validatorIndices, big.NewInt(int64(position))) - } - } - signatures := [][]byte{} validatorAddresses := []common.Address{} validatorAddressProofs := [][][32]byte{} for _, validatorIndex := range validatorIndices { - ok, beefySig := r.SignedCommitment.Signatures[validatorIndex.Int64()].Unwrap() + ok, beefySig := r.SignedCommitment.Signatures[validatorIndex].Unwrap() if !ok { return nil, fmt.Errorf("signature is empty") } signatures = append(signatures, cleanSignature(beefySig)) - pubKey := r.Validators[validatorIndex.Int64()] + pubKey := r.Validators[validatorIndex] address, err := pubKey.IntoEthereumAddress() if err != nil { @@ -128,7 +117,7 @@ func (r *Request) MakeSubmitFinalParams(validationID int64, bitfield string) (*F validatorAddresses = append(validatorAddresses, address) - merkleProof, err := r.generateValidatorAddressProof(validatorIndex.Int64()) + merkleProof, err := r.generateValidatorAddressProof(int64(validatorIndex)) if err != nil { return nil, err } @@ -167,12 +156,17 @@ func (r *Request) MakeSubmitFinalParams(validationID int64, bitfield string) (*F Order: r.Proof.MerkleProofOrder, } + validatorIndicesBigInt := []*big.Int{} + for _, index := range validatorIndices { + validatorIndicesBigInt = append(validatorIndicesBigInt, new(big.Int).SetUint64(index)) + } + msg := FinalRequestParams{ ID: validationDataID, Commitment: commitment, Proof: beefyclient.BeefyClientValidatorMultiProof{ Signatures: signatures, - Indices: validatorIndices, + Indices: validatorIndicesBigInt, Addrs: validatorAddresses, MerkleProofs: validatorAddressProofs, },