github.com/mavryk-network/mvgo@v1.19.9/examples/doublebake/main.go (about)

     1  // ☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️
     2  //
     3  // ATTENTION!! THIS PROGRAM WILL BURN YOUR FUNDS!! HANDLE WITH CARE
     4  //
     5  // ☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️☠️
     6  //
     7  // Generates fake double_endorsement_evidence for the given baker
     8  // which triggers slashing code in the protocol.
     9  //
    10  // How it works
    11  // - watch for next block, read level, round, payload hash
    12  // - check if baker has endorsement rights for this block, extract slot
    13  // - create and sign 2 endorsement ops from real + random payload hashes
    14  // - wait one block
    15  // - send the 2 signed endorsements in as double_endorsement_evidence
    16  //
    17  // To specify the baker key set env var MVGO_PRIVATE_KEY
    18  
    19  package main
    20  
    21  import (
    22  	"bytes"
    23  	"context"
    24  	"crypto/rand"
    25  	"errors"
    26  	"flag"
    27  	"fmt"
    28  	"os"
    29  	"os/signal"
    30  	"syscall"
    31  	"time"
    32  
    33  	"github.com/echa/log"
    34  	"github.com/mavryk-network/mvgo/codec"
    35  	"github.com/mavryk-network/mvgo/mavryk"
    36  	"github.com/mavryk-network/mvgo/rpc"
    37  	"github.com/mavryk-network/mvgo/signer"
    38  	"golang.org/x/crypto/blake2b"
    39  )
    40  
    41  var (
    42  	flags   = flag.NewFlagSet("doublex", flag.ContinueOnError)
    43  	verbose bool
    44  	node    string
    45  	sk      mavryk.PrivateKey
    46  )
    47  
    48  func init() {
    49  	if k := os.Getenv("MVGO_PRIVATE_KEY"); k != "" {
    50  		sk = mavryk.MustParsePrivateKey(k)
    51  	}
    52  	flags.Usage = func() {}
    53  	flags.BoolVar(&verbose, "v", false, "be verbose")
    54  	flags.StringVar(&node, "node", "https://atlasnet.rpc.mavryk.network", "Mavryk node URL")
    55  }
    56  
    57  func main() {
    58  	if err := flags.Parse(os.Args[1:]); err != nil {
    59  		if err == flag.ErrHelp {
    60  			fmt.Println("Usage: doublex [args]")
    61  			fmt.Println("\nArguments")
    62  			flags.PrintDefaults()
    63  			os.Exit(0)
    64  		}
    65  		fmt.Println("Error:", err)
    66  		return
    67  	}
    68  
    69  	switch {
    70  	case verbose:
    71  		log.SetLevel(log.LevelTrace)
    72  	default:
    73  		log.SetLevel(log.LevelInfo)
    74  	}
    75  	rpc.UseLogger(log.Log)
    76  
    77  	if err := run(); err != nil {
    78  		if !errors.Is(err, context.Canceled) {
    79  			fmt.Println("Error:", err)
    80  		}
    81  	}
    82  }
    83  
    84  func run() error {
    85  	if !sk.IsValid() {
    86  		return fmt.Errorf("missing secret key")
    87  	}
    88  
    89  	ctx, cancel := context.WithCancel(context.Background())
    90  	defer cancel()
    91  
    92  	c, err := rpc.NewClient(node, nil)
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	log.Info("Using key for baker ", sk.Address())
    98  	c.Signer = signer.NewFromKey(sk)
    99  
   100  	if err := c.Init(ctx); err != nil {
   101  		return err
   102  	}
   103  	return monitorBlocks(ctx, c)
   104  }
   105  
   106  // watch for next block with endorsing rights
   107  func monitorBlocks(ctx context.Context, c *rpc.Client) error {
   108  	mon := rpc.NewBlockHeaderMonitor()
   109  	defer mon.Close()
   110  	if err := c.MonitorBlockHeader(ctx, mon); err != nil {
   111  		return err
   112  	}
   113  
   114  	ctx2, cancel := context.WithCancel(ctx)
   115  	stop := make(chan os.Signal, 1)
   116  	signal.Notify(stop,
   117  		syscall.SIGHUP,
   118  		syscall.SIGINT,
   119  		syscall.SIGTERM,
   120  		syscall.SIGQUIT,
   121  	)
   122  	go func() {
   123  		select {
   124  		case <-stop:
   125  			log.Info("Stopping monitor")
   126  			cancel()
   127  		case <-ctx.Done():
   128  		}
   129  	}()
   130  
   131  	log.Info("Waiting for new blocks... (cancel with Ctrl-C)")
   132  	for {
   133  		h, err := mon.Recv(ctx2)
   134  		if err != nil {
   135  			return err
   136  		}
   137  		select {
   138  		case <-ctx2.Done():
   139  			return nil
   140  		default:
   141  		}
   142  		if err := handleBlock(ctx2, c, h); err != nil {
   143  			log.Error(err)
   144  		}
   145  	}
   146  }
   147  
   148  var evidence *codec.TenderbakeDoubleEndorsementEvidence
   149  
   150  func handleBlock(ctx context.Context, c *rpc.Client, b *rpc.BlockHeaderLogEntry) error {
   151  	// send evidence in next block
   152  	if evidence != nil {
   153  		log.Infof("Block %d %s round=%d", b.Level, b.Hash, b.Round())
   154  		ev := evidence
   155  		evidence = nil
   156  		return sendDoubleEndorse(ctx, c, b, ev)
   157  	}
   158  
   159  	// prepare evidence
   160  	slot, ok, err := fetchEndorsingRights(ctx, c, b.Hash)
   161  	if err != nil {
   162  		return err
   163  	}
   164  	if !ok {
   165  		log.Infof("Block %d %s round=%d", b.Level, b.Hash, b.Round())
   166  		return nil
   167  	}
   168  	log.Infof("Block %d %s round=%d with endorsing rights", b.Level, b.Hash, b.Round())
   169  
   170  	evidence = createDoubleEndorse(c, b, slot)
   171  	return nil
   172  }
   173  
   174  func fetchEndorsingRights(ctx context.Context, c *rpc.Client, id mavryk.BlockHash) (int, bool, error) {
   175  	u := fmt.Sprintf("chains/main/blocks/%s/helpers/endorsing_rights?delegate=%s", id, sk.Address())
   176  	var rights []struct {
   177  		Level         int64                `json:"level"`
   178  		Delegates     []rpc.EndorsingRight `json:"delegates"`
   179  		EstimatedTime time.Time            `json:"estimated_time"`
   180  	}
   181  	if err := c.Get(ctx, u, &rights); err != nil {
   182  		return 0, false, err
   183  	}
   184  	if len(rights) == 0 {
   185  		return 0, false, nil
   186  	}
   187  	return rights[0].Delegates[0].FirstSlot, true, nil
   188  }
   189  
   190  func createDoubleEndorse(c *rpc.Client, b *rpc.BlockHeaderLogEntry, slot int) *codec.TenderbakeDoubleEndorsementEvidence {
   191  	log.Infof("Creating 2endorse evidence")
   192  	o1, oh1 := signEndorsement(c, b, slot, false)
   193  	o2, oh2 := signEndorsement(c, b, slot, true)
   194  	// FIXME: order endorsements by op hash
   195  	if bytes.Compare(oh1[:], oh2[:]) > 0 {
   196  		o1, o2 = o2, o1
   197  	}
   198  	return &codec.TenderbakeDoubleEndorsementEvidence{
   199  		Op1: o1,
   200  		Op2: o2,
   201  	}
   202  }
   203  
   204  func sendDoubleEndorse(ctx context.Context, c *rpc.Client, b *rpc.BlockHeaderLogEntry, ev *codec.TenderbakeDoubleEndorsementEvidence) error {
   205  	op := codec.NewOp().
   206  		WithParams(c.Params). // use protocol params for correct op tags
   207  		WithContents(ev).
   208  		WithBranch(b.Hash)
   209  	buf, _ := op.MarshalJSON()
   210  	log.Infof("Sending 2endorse: %s", string(buf))
   211  	res, err := c.Send(ctx, op, &rpc.CallOptions{IgnoreLimits: true})
   212  	if err != nil {
   213  		return err
   214  	}
   215  	if !res.IsSuccess() {
   216  		return res.Error()
   217  	}
   218  	log.Infof("Success")
   219  	return nil
   220  }
   221  
   222  func signEndorsement(c *rpc.Client, b *rpc.BlockHeaderLogEntry, slot int, random bool) (codec.TenderbakeInlinedEndorsement, mavryk.OpHash) {
   223  	e := codec.TenderbakeEndorsement{
   224  		Slot:             int16(slot),
   225  		Level:            int32(b.Level),
   226  		Round:            int32(b.Round()),
   227  		BlockPayloadHash: b.PayloadHash(),
   228  	}
   229  	if random {
   230  		_, _ = rand.Read(e.BlockPayloadHash[:])
   231  	}
   232  	op := codec.NewOp().
   233  		WithParams(c.Params).   // use protocol params for correct op tags
   234  		WithChainId(c.ChainId). // Tenderbake endorsements need chain id
   235  		WithContents(&e).
   236  		WithBranch(b.Hash)
   237  	if err := op.Sign(sk); err != nil {
   238  		log.Errorf("signing endorsement: %v", err)
   239  	}
   240  	return codec.TenderbakeInlinedEndorsement{
   241  		Branch:      b.Hash,
   242  		Endorsement: e,
   243  		Signature:   op.Signature,
   244  	}, ophash(op.Digest())
   245  }
   246  
   247  // FIXME: what's the correct method to calculate op hash from contents?
   248  func ophash(buf []byte) (oh mavryk.OpHash) {
   249  	h, _ := blake2b.New(32, nil)
   250  	h.Write(buf)
   251  	copy(oh[:], h.Sum(nil))
   252  	return
   253  }