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 }