github.com/filecoin-project/specs-actors/v4@v4.0.2/actors/builtin/multisig/multisig_actor.go (about) 1 package multisig 2 3 import ( 4 "bytes" 5 "fmt" 6 7 addr "github.com/filecoin-project/go-address" 8 "github.com/filecoin-project/go-state-types/abi" 9 "github.com/filecoin-project/go-state-types/big" 10 "github.com/filecoin-project/go-state-types/cbor" 11 "github.com/filecoin-project/go-state-types/exitcode" 12 multisig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" 13 multisig2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/multisig" 14 15 "github.com/ipfs/go-cid" 16 17 "github.com/filecoin-project/specs-actors/v4/actors/builtin" 18 "github.com/filecoin-project/specs-actors/v4/actors/runtime" 19 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" 20 ) 21 22 type TxnID = multisig0.TxnID 23 24 //type Transaction struct { 25 // To addr.Address 26 // Value abi.TokenAmount 27 // Method abi.MethodNum 28 // Params []byte 29 // 30 // // This address at index 0 is the transaction proposer, order of this slice must be preserved. 31 // Approved []addr.Address 32 //} 33 type Transaction = multisig0.Transaction 34 35 // Data for a BLAKE2B-256 to be attached to methods referencing proposals via TXIDs. 36 // Ensures the existence of a cryptographic reference to the original proposal. Useful 37 // for offline signers and for protection when reorgs change a multisig TXID. 38 // 39 // Requester - The requesting multisig wallet member. 40 // All other fields - From the "Transaction" struct. 41 //type ProposalHashData struct { 42 // Requester addr.Address 43 // To addr.Address 44 // Value abi.TokenAmount 45 // Method abi.MethodNum 46 // Params []byte 47 //} 48 type ProposalHashData = multisig0.ProposalHashData 49 50 type Actor struct{} 51 52 func (a Actor) Exports() []interface{} { 53 return []interface{}{ 54 builtin.MethodConstructor: a.Constructor, 55 2: a.Propose, 56 3: a.Approve, 57 4: a.Cancel, 58 5: a.AddSigner, 59 6: a.RemoveSigner, 60 7: a.SwapSigner, 61 8: a.ChangeNumApprovalsThreshold, 62 9: a.LockBalance, 63 } 64 } 65 66 func (a Actor) Code() cid.Cid { 67 return builtin.MultisigActorCodeID 68 } 69 70 func (a Actor) State() cbor.Er { 71 return new(State) 72 } 73 74 var _ runtime.VMActor = Actor{} 75 76 // type ConstructorParams struct { 77 // Signers []addr.Address 78 // NumApprovalsThreshold uint64 79 // UnlockDuration abi.ChainEpoch 80 // StartEpoch abi.ChainEpoch 81 // } 82 type ConstructorParams = multisig2.ConstructorParams 83 84 func (a Actor) Constructor(rt runtime.Runtime, params *ConstructorParams) *abi.EmptyValue { 85 rt.ValidateImmediateCallerIs(builtin.InitActorAddr) 86 87 if len(params.Signers) < 1 { 88 rt.Abortf(exitcode.ErrIllegalArgument, "must have at least one signer") 89 } 90 91 if len(params.Signers) > SignersMax { 92 rt.Abortf(exitcode.ErrIllegalArgument, "cannot add more than %d signers", SignersMax) 93 } 94 95 // resolve signer addresses and do not allow duplicate signers 96 resolvedSigners := make([]addr.Address, 0, len(params.Signers)) 97 deDupSigners := make(map[addr.Address]struct{}, len(params.Signers)) 98 for _, signer := range params.Signers { 99 resolved, err := builtin.ResolveToIDAddr(rt, signer) 100 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve addr %v to ID addr", signer) 101 102 if _, ok := deDupSigners[resolved]; ok { 103 rt.Abortf(exitcode.ErrIllegalArgument, "duplicate signer not allowed: %s", signer) 104 } 105 106 resolvedSigners = append(resolvedSigners, resolved) 107 deDupSigners[resolved] = struct{}{} 108 } 109 110 if params.NumApprovalsThreshold > uint64(len(params.Signers)) { 111 rt.Abortf(exitcode.ErrIllegalArgument, "must not require more approvals than signers") 112 } 113 114 if params.NumApprovalsThreshold < 1 { 115 rt.Abortf(exitcode.ErrIllegalArgument, "must require at least one approval") 116 } 117 118 if params.UnlockDuration < 0 { 119 rt.Abortf(exitcode.ErrIllegalArgument, "negative unlock duration disallowed") 120 } 121 122 pending, err := adt.StoreEmptyMap(adt.AsStore(rt), builtin.DefaultHamtBitwidth) 123 if err != nil { 124 rt.Abortf(exitcode.ErrIllegalState, "failed to create empty map: %v", err) 125 } 126 127 var st State 128 st.Signers = resolvedSigners 129 st.NumApprovalsThreshold = params.NumApprovalsThreshold 130 st.PendingTxns = pending 131 st.InitialBalance = abi.NewTokenAmount(0) 132 if params.UnlockDuration != 0 { 133 st.SetLocked(params.StartEpoch, params.UnlockDuration, rt.ValueReceived()) 134 } 135 136 rt.StateCreate(&st) 137 return nil 138 } 139 140 //type ProposeParams struct { 141 // To addr.Address 142 // Value abi.TokenAmount 143 // Method abi.MethodNum 144 // Params []byte 145 //} 146 type ProposeParams = multisig0.ProposeParams 147 148 //type ProposeReturn struct { 149 // // TxnID is the ID of the proposed transaction 150 // TxnID TxnID 151 // // Applied indicates if the transaction was applied as opposed to proposed but not applied due to lack of approvals 152 // Applied bool 153 // // Code is the exitcode of the transaction, if Applied is false this field should be ignored. 154 // Code exitcode.ExitCode 155 // // Ret is the return vale of the transaction, if Applied is false this field should be ignored. 156 // Ret []byte 157 //} 158 type ProposeReturn = multisig0.ProposeReturn 159 160 func (a Actor) Propose(rt runtime.Runtime, params *ProposeParams) *ProposeReturn { 161 rt.ValidateImmediateCallerType(builtin.CallerTypesSignable...) 162 proposer := rt.Caller() 163 164 if params.Value.Sign() < 0 { 165 rt.Abortf(exitcode.ErrIllegalArgument, "proposed value must be non-negative, was %v", params.Value) 166 } 167 168 var txnID TxnID 169 var st State 170 var txn *Transaction 171 rt.StateTransaction(&st, func() { 172 if !st.IsSigner(proposer) { 173 rt.Abortf(exitcode.ErrForbidden, "%s is not a signer", proposer) 174 } 175 176 ptx, err := adt.AsMap(adt.AsStore(rt), st.PendingTxns, builtin.DefaultHamtBitwidth) 177 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load pending transactions") 178 179 txnID = st.NextTxnID 180 st.NextTxnID += 1 181 txn = &Transaction{ 182 To: params.To, 183 Value: params.Value, 184 Method: params.Method, 185 Params: params.Params, 186 Approved: []addr.Address{}, 187 } 188 189 if err := ptx.Put(txnID, txn); err != nil { 190 rt.Abortf(exitcode.ErrIllegalState, "failed to put transaction for propose: %v", err) 191 } 192 193 st.PendingTxns, err = ptx.Root() 194 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush pending transactions") 195 }) 196 197 applied, ret, code := a.approveTransaction(rt, txnID, txn) 198 199 // Note: this transaction ID may not be stable across chain re-orgs. 200 // The proposal hash may be provided as a stability check when approving. 201 return &ProposeReturn{ 202 TxnID: txnID, 203 Applied: applied, 204 Code: code, 205 Ret: ret, 206 } 207 } 208 209 //type TxnIDParams struct { 210 // ID TxnID 211 // // Optional hash of proposal to ensure an operation can only apply to a 212 // // specific proposal. 213 // ProposalHash []byte 214 //} 215 type TxnIDParams = multisig0.TxnIDParams 216 217 //type ApproveReturn struct { 218 // // Applied indicates if the transaction was applied as opposed to proposed but not applied due to lack of approvals 219 // Applied bool 220 // // Code is the exitcode of the transaction, if Applied is false this field should be ignored. 221 // Code exitcode.ExitCode 222 // // Ret is the return vale of the transaction, if Applied is false this field should be ignored. 223 // Ret []byte 224 //} 225 type ApproveReturn = multisig0.ApproveReturn 226 227 func (a Actor) Approve(rt runtime.Runtime, params *TxnIDParams) *ApproveReturn { 228 rt.ValidateImmediateCallerType(builtin.CallerTypesSignable...) 229 approver := rt.Caller() 230 231 var st State 232 var txn *Transaction 233 rt.StateTransaction(&st, func() { 234 if !st.IsSigner(approver) { 235 rt.Abortf(exitcode.ErrForbidden, "%s is not a signer", approver) 236 } 237 238 ptx, err := adt.AsMap(adt.AsStore(rt), st.PendingTxns, builtin.DefaultHamtBitwidth) 239 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load pending transactions") 240 241 txn = getTransaction(rt, ptx, params.ID, params.ProposalHash, true) 242 }) 243 244 // if the transaction already has enough approvers, execute it without "processing" this approval. 245 approved, ret, code := executeTransactionIfApproved(rt, st, params.ID, txn) 246 if !approved { 247 // if the transaction hasn't already been approved, let's "process" this approval 248 // and see if we can execute the transaction 249 approved, ret, code = a.approveTransaction(rt, params.ID, txn) 250 } 251 252 return &ApproveReturn{ 253 Applied: approved, 254 Code: code, 255 Ret: ret, 256 } 257 } 258 259 func (a Actor) Cancel(rt runtime.Runtime, params *TxnIDParams) *abi.EmptyValue { 260 rt.ValidateImmediateCallerType(builtin.CallerTypesSignable...) 261 callerAddr := rt.Caller() 262 263 var st State 264 rt.StateTransaction(&st, func() { 265 callerIsSigner := st.IsSigner(callerAddr) 266 if !callerIsSigner { 267 rt.Abortf(exitcode.ErrForbidden, "%s is not a signer", callerAddr) 268 } 269 270 ptx, err := adt.AsMap(adt.AsStore(rt), st.PendingTxns, builtin.DefaultHamtBitwidth) 271 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load pending txns") 272 273 var txn Transaction 274 found, err := ptx.Pop(params.ID, &txn) 275 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to pop transaction %v for cancel", params.ID) 276 if !found { 277 rt.Abortf(exitcode.ErrNotFound, "no such transaction %v to cancel", params.ID) 278 } 279 280 proposer := txn.Approved[0] 281 if proposer != callerAddr { 282 rt.Abortf(exitcode.ErrForbidden, "Cannot cancel another signers transaction") 283 } 284 285 // confirm the hashes match 286 calculatedHash, err := ComputeProposalHash(&txn, rt.HashBlake2b) 287 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to compute proposal hash for %v", params.ID) 288 if params.ProposalHash != nil && !bytes.Equal(params.ProposalHash, calculatedHash[:]) { 289 rt.Abortf(exitcode.ErrIllegalState, "hash does not match proposal params (ensure requester is an ID address)") 290 } 291 292 st.PendingTxns, err = ptx.Root() 293 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush pending transactions") 294 }) 295 return nil 296 } 297 298 //type AddSignerParams struct { 299 // Signer addr.Address 300 // Increase bool 301 //} 302 type AddSignerParams = multisig0.AddSignerParams 303 304 func (a Actor) AddSigner(rt runtime.Runtime, params *AddSignerParams) *abi.EmptyValue { 305 // Can only be called by the multisig wallet itself. 306 rt.ValidateImmediateCallerIs(rt.Receiver()) 307 resolvedNewSigner, err := builtin.ResolveToIDAddr(rt, params.Signer) 308 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve address %v", params.Signer) 309 310 var st State 311 rt.StateTransaction(&st, func() { 312 if len(st.Signers) >= SignersMax { 313 rt.Abortf(exitcode.ErrForbidden, "cannot add more than %d signers", SignersMax) 314 } 315 316 if st.IsSigner(resolvedNewSigner) { 317 rt.Abortf(exitcode.ErrForbidden, "%s is already a signer", resolvedNewSigner) 318 } 319 320 st.Signers = append(st.Signers, resolvedNewSigner) 321 if params.Increase { 322 st.NumApprovalsThreshold = st.NumApprovalsThreshold + 1 323 } 324 }) 325 return nil 326 } 327 328 //type RemoveSignerParams struct { 329 // Signer addr.Address 330 // Decrease bool 331 //} 332 type RemoveSignerParams = multisig0.RemoveSignerParams 333 334 func (a Actor) RemoveSigner(rt runtime.Runtime, params *RemoveSignerParams) *abi.EmptyValue { 335 // Can only be called by the multisig wallet itself. 336 rt.ValidateImmediateCallerIs(rt.Receiver()) 337 resolvedOldSigner, err := builtin.ResolveToIDAddr(rt, params.Signer) 338 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve address %v", params.Signer) 339 340 store := adt.AsStore(rt) 341 var st State 342 rt.StateTransaction(&st, func() { 343 if !st.IsSigner(resolvedOldSigner) { 344 rt.Abortf(exitcode.ErrForbidden, "%s is not a signer", resolvedOldSigner) 345 } 346 347 if len(st.Signers) == 1 { 348 rt.Abortf(exitcode.ErrForbidden, "cannot remove only signer") 349 } 350 351 newSigners := make([]addr.Address, 0, len(st.Signers)) 352 // signers have already been resolved 353 for _, s := range st.Signers { 354 if resolvedOldSigner != s { 355 newSigners = append(newSigners, s) 356 } 357 } 358 359 // if the number of signers is below the threshold after removing the given signer, 360 // we should decrease the threshold by 1. This means that decrease should NOT be set to false 361 // in such a scenario. 362 if !params.Decrease && uint64(len(st.Signers)-1) < st.NumApprovalsThreshold { 363 rt.Abortf(exitcode.ErrIllegalArgument, "can't reduce signers to %d below threshold %d with decrease=false", len(st.Signers)-1, st.NumApprovalsThreshold) 364 } 365 366 if params.Decrease { 367 if st.NumApprovalsThreshold < 2 { 368 rt.Abortf(exitcode.ErrIllegalArgument, "can't decrease approvals from %d to %d", st.NumApprovalsThreshold, st.NumApprovalsThreshold-1) 369 } 370 st.NumApprovalsThreshold = st.NumApprovalsThreshold - 1 371 } 372 373 err := st.PurgeApprovals(store, resolvedOldSigner) 374 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to purge approvals of removed signer") 375 376 st.Signers = newSigners 377 }) 378 379 return nil 380 } 381 382 //type SwapSignerParams struct { 383 // From addr.Address 384 // To addr.Address 385 //} 386 type SwapSignerParams = multisig0.SwapSignerParams 387 388 func (a Actor) SwapSigner(rt runtime.Runtime, params *SwapSignerParams) *abi.EmptyValue { 389 // Can only be called by the multisig wallet itself. 390 rt.ValidateImmediateCallerIs(rt.Receiver()) 391 392 fromResolved, err := builtin.ResolveToIDAddr(rt, params.From) 393 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve from address %v", params.From) 394 395 toResolved, err := builtin.ResolveToIDAddr(rt, params.To) 396 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve to address %v", params.To) 397 398 store := adt.AsStore(rt) 399 var st State 400 rt.StateTransaction(&st, func() { 401 if !st.IsSigner(fromResolved) { 402 rt.Abortf(exitcode.ErrForbidden, "from addr %s is not a signer", fromResolved) 403 } 404 405 if st.IsSigner(toResolved) { 406 rt.Abortf(exitcode.ErrIllegalArgument, "%s already a signer", toResolved) 407 } 408 409 newSigners := make([]addr.Address, 0, len(st.Signers)) 410 for _, s := range st.Signers { 411 if s != fromResolved { 412 newSigners = append(newSigners, s) 413 } 414 } 415 newSigners = append(newSigners, toResolved) 416 st.Signers = newSigners 417 418 err := st.PurgeApprovals(store, fromResolved) 419 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to purge approvals of removed signer") 420 }) 421 422 return nil 423 } 424 425 //type ChangeNumApprovalsThresholdParams struct { 426 // NewThreshold uint64 427 //} 428 type ChangeNumApprovalsThresholdParams = multisig0.ChangeNumApprovalsThresholdParams 429 430 func (a Actor) ChangeNumApprovalsThreshold(rt runtime.Runtime, params *ChangeNumApprovalsThresholdParams) *abi.EmptyValue { 431 // Can only be called by the multisig wallet itself. 432 rt.ValidateImmediateCallerIs(rt.Receiver()) 433 434 var st State 435 rt.StateTransaction(&st, func() { 436 if params.NewThreshold == 0 || params.NewThreshold > uint64(len(st.Signers)) { 437 rt.Abortf(exitcode.ErrIllegalArgument, "New threshold value not supported") 438 } 439 440 st.NumApprovalsThreshold = params.NewThreshold 441 }) 442 return nil 443 } 444 445 //type LockBalanceParams struct { 446 // StartEpoch abi.ChainEpoch 447 // UnlockDuration abi.ChainEpoch 448 // Amount abi.TokenAmount 449 //} 450 type LockBalanceParams = multisig0.LockBalanceParams 451 452 func (a Actor) LockBalance(rt runtime.Runtime, params *LockBalanceParams) *abi.EmptyValue { 453 // Can only be called by the multisig wallet itself. 454 rt.ValidateImmediateCallerIs(rt.Receiver()) 455 456 if params.UnlockDuration <= 0 { 457 // Note: Unlock duration of zero is workable, but rejected as ineffective, probably an error. 458 rt.Abortf(exitcode.ErrIllegalArgument, "unlock duration must be positive") 459 } 460 461 if params.Amount.LessThan(big.Zero()) { 462 rt.Abortf(exitcode.ErrIllegalArgument, "amount to lock must be positive") 463 } 464 465 var st State 466 rt.StateTransaction(&st, func() { 467 if st.UnlockDuration != 0 { 468 rt.Abortf(exitcode.ErrForbidden, "modification of unlock disallowed") 469 } 470 st.SetLocked(params.StartEpoch, params.UnlockDuration, params.Amount) 471 }) 472 return nil 473 } 474 475 func (a Actor) approveTransaction(rt runtime.Runtime, txnID TxnID, txn *Transaction) (bool, []byte, exitcode.ExitCode) { 476 caller := rt.Caller() 477 478 var st State 479 // abort duplicate approval 480 for _, previousApprover := range txn.Approved { 481 if previousApprover == caller { 482 rt.Abortf(exitcode.ErrForbidden, "%s already approved this message", previousApprover) 483 } 484 } 485 486 // add the caller to the list of approvers 487 rt.StateTransaction(&st, func() { 488 ptx, err := adt.AsMap(adt.AsStore(rt), st.PendingTxns, builtin.DefaultHamtBitwidth) 489 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load pending transactions") 490 491 // update approved on the transaction 492 txn.Approved = append(txn.Approved, caller) 493 err = ptx.Put(txnID, txn) 494 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to put transaction %v for approval", txnID) 495 496 st.PendingTxns, err = ptx.Root() 497 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush pending transactions") 498 }) 499 500 return executeTransactionIfApproved(rt, st, txnID, txn) 501 } 502 503 func getTransaction(rt runtime.Runtime, ptx *adt.Map, txnID TxnID, proposalHash []byte, checkHash bool) *Transaction { 504 // get transaction from the state trie 505 var txn Transaction 506 found, err := ptx.Get(txnID, &txn) 507 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load transaction %v for approval", txnID) 508 if !found { 509 rt.Abortf(exitcode.ErrNotFound, "no such transaction %v for approval", txnID) 510 } 511 512 // confirm the hashes match 513 if checkHash { 514 calculatedHash, err := ComputeProposalHash(&txn, rt.HashBlake2b) 515 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to compute proposal hash for %v", txnID) 516 if proposalHash != nil && !bytes.Equal(proposalHash, calculatedHash[:]) { 517 rt.Abortf(exitcode.ErrIllegalArgument, "hash does not match proposal params (ensure requester is an ID address)") 518 } 519 } 520 521 return &txn 522 } 523 524 func executeTransactionIfApproved(rt runtime.Runtime, st State, txnID TxnID, txn *Transaction) (bool, []byte, exitcode.ExitCode) { 525 var out builtin.CBORBytes 526 var code exitcode.ExitCode 527 applied := false 528 529 thresholdMet := uint64(len(txn.Approved)) >= st.NumApprovalsThreshold 530 if thresholdMet { 531 if err := st.assertAvailable(rt.CurrentBalance(), txn.Value, rt.CurrEpoch()); err != nil { 532 rt.Abortf(exitcode.ErrInsufficientFunds, "insufficient funds unlocked: %v", err) 533 } 534 535 // A sufficient number of approvals have arrived and sufficient funds have been unlocked: relay the message and delete from pending queue. 536 code = rt.Send( 537 txn.To, 538 txn.Method, 539 builtin.CBORBytes(txn.Params), 540 txn.Value, 541 &out, 542 ) 543 applied = true 544 545 // This could be rearranged to happen inside the first state transaction, before the send(). 546 rt.StateTransaction(&st, func() { 547 ptx, err := adt.AsMap(adt.AsStore(rt), st.PendingTxns, builtin.DefaultHamtBitwidth) 548 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load pending transactions") 549 550 // Allow transaction not to be found when deleting. 551 // This allows 1 out of n multisig swaps and removes initiated by the swapped/removed signer to go through cleanly. 552 if _, err := ptx.TryDelete(txnID); err != nil { 553 rt.Abortf(exitcode.ErrIllegalState, "failed to delete transaction for cleanup: %v", err) 554 } 555 556 st.PendingTxns, err = ptx.Root() 557 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush pending transactions") 558 }) 559 } 560 561 // Pass the return value through uninterpreted with the expectation that serializing into a CBORBytes never fails 562 // since it just copies the bytes. 563 564 return applied, out, code 565 } 566 567 // Computes a digest of a proposed transaction. This digest is used to confirm identity of the transaction 568 // associated with an ID, which might change under chain re-orgs. 569 func ComputeProposalHash(txn *Transaction, hash func([]byte) [32]byte) ([]byte, error) { 570 hashData := ProposalHashData{ 571 Requester: txn.Approved[0], 572 To: txn.To, 573 Value: txn.Value, 574 Method: txn.Method, 575 Params: txn.Params, 576 } 577 578 data, err := hashData.Serialize() 579 if err != nil { 580 return nil, fmt.Errorf("failed to construct multisig approval hash: %w", err) 581 } 582 583 hashResult := hash(data) 584 return hashResult[:], nil 585 }