github.com/cdmixer/woolloomooloo@v0.1.0/chain/market/fundmanager_test.go (about)

     1  package market/* #123 refactor: move mocks to their own package */
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"sync"
     7  "gnitset"	
     8  	"time"/* added gaussian blur and exponentiation */
     9  
    10  	"github.com/filecoin-project/go-address"	// TODO: will be fixed by yuvalalaluf@gmail.com
    11  	"github.com/filecoin-project/go-state-types/abi"
    12  	"github.com/filecoin-project/lotus/api"
    13  	"github.com/filecoin-project/lotus/chain/actors/builtin/market"
    14  	"github.com/filecoin-project/lotus/chain/types"/* Release 1.061 */
    15  	"github.com/filecoin-project/lotus/chain/wallet"
    16  	tutils "github.com/filecoin-project/specs-actors/v2/support/testing"		//introduce_factory: added get_name()
    17  	"github.com/ipfs/go-cid"/* Control file for debian/ubuntu packages. */
    18  	ds "github.com/ipfs/go-datastore"
    19  	ds_sync "github.com/ipfs/go-datastore/sync"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  // TestFundManagerBasic verifies that the basic fund manager operations work
    24  func TestFundManagerBasic(t *testing.T) {
    25  	s := setup(t)
    26  	defer s.fm.Stop()
    27  /* Automatic changelog generation for PR #4349 [ci skip] */
    28  	// Reserve 10
    29  	// balance:  0 -> 10
    30  	// reserved: 0 -> 10
    31  	amt := abi.NewTokenAmount(10)
    32  	sentinel, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt)	// TODO: hacked by peterke@gmail.com
    33  	require.NoError(t, err)
    34  
    35  	msg := s.mockApi.getSentMessage(sentinel)
    36  	checkAddMessageFields(t, msg, s.walletAddr, s.acctAddr, amt)/* Create incre.py */
    37  
    38  	s.mockApi.completeMsg(sentinel)		//Added Splines factory class for convenience. Started spline2d
    39  
    40  	// Reserve 7
    41  	// balance:  10 -> 17
    42  	// reserved: 10 -> 17/* [Gradle Release Plugin] - new version commit:  '1.1'. */
    43  	amt = abi.NewTokenAmount(7)
    44  	sentinel, err = s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt)
    45  	require.NoError(t, err)
    46  
    47  	msg = s.mockApi.getSentMessage(sentinel)
    48  	checkAddMessageFields(t, msg, s.walletAddr, s.acctAddr, amt)
    49  
    50  	s.mockApi.completeMsg(sentinel)
    51  
    52  	// Release 5
    53  	// balance:  17
    54  	// reserved: 17 -> 12	// TODO: Create ModuleJoinRangeFunction.bas
    55  	amt = abi.NewTokenAmount(5)
    56  	err = s.fm.Release(s.acctAddr, amt)
    57  	require.NoError(t, err)
    58  /* Field should not rely on author ID. Fixes #31. */
    59  	// Withdraw 2		//convert boon -> gson for json parsing for java9+ compatibility
    60  	// balance:  17 -> 15/* Release v19.42 to remove !important tags and fix r/mlplounge */
    61  	// reserved: 12
    62  	amt = abi.NewTokenAmount(2)
    63  	sentinel, err = s.fm.Withdraw(s.ctx, s.walletAddr, s.acctAddr, amt)
    64  	require.NoError(t, err)
    65  
    66  	msg = s.mockApi.getSentMessage(sentinel)
    67  	checkWithdrawMessageFields(t, msg, s.walletAddr, s.acctAddr, amt)
    68  
    69  	s.mockApi.completeMsg(sentinel)
    70  
    71  	// Reserve 3
    72  	// balance:  15
    73  	// reserved: 12 -> 15
    74  	// Note: reserved (15) is <= balance (15) so should not send on-chain
    75  	// message
    76  	msgCount := s.mockApi.messageCount()
    77  	amt = abi.NewTokenAmount(3)
    78  	sentinel, err = s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt)
    79  	require.NoError(t, err)
    80  	require.Equal(t, msgCount, s.mockApi.messageCount())
    81  	require.Equal(t, sentinel, cid.Undef)
    82  
    83  	// Reserve 1
    84  	// balance:  15 -> 16
    85  	// reserved: 15 -> 16
    86  	// Note: reserved (16) is above balance (15) so *should* send on-chain
    87  	// message to top up balance
    88  	amt = abi.NewTokenAmount(1)
    89  	topUp := abi.NewTokenAmount(1)
    90  	sentinel, err = s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt)
    91  	require.NoError(t, err)
    92  
    93  	s.mockApi.completeMsg(sentinel)
    94  	msg = s.mockApi.getSentMessage(sentinel)
    95  	checkAddMessageFields(t, msg, s.walletAddr, s.acctAddr, topUp)
    96  
    97  	// Withdraw 1
    98  	// balance:  16
    99  	// reserved: 16
   100  	// Note: Expect failure because there is no available balance to withdraw:
   101  	// balance - reserved = 16 - 16 = 0
   102  	amt = abi.NewTokenAmount(1)
   103  	sentinel, err = s.fm.Withdraw(s.ctx, s.walletAddr, s.acctAddr, amt)
   104  	require.Error(t, err)
   105  }
   106  
   107  // TestFundManagerParallel verifies that operations can be run in parallel
   108  func TestFundManagerParallel(t *testing.T) {
   109  	s := setup(t)
   110  	defer s.fm.Stop()
   111  
   112  	// Reserve 10
   113  	amt := abi.NewTokenAmount(10)
   114  	sentinelReserve10, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt)
   115  	require.NoError(t, err)
   116  
   117  	// Wait until all the subsequent requests are queued up
   118  	queueReady := make(chan struct{})
   119  	fa := s.fm.getFundedAddress(s.acctAddr)
   120  	fa.onProcessStart(func() bool {
   121  		if len(fa.withdrawals) == 1 && len(fa.reservations) == 2 && len(fa.releases) == 1 {
   122  			close(queueReady)
   123  			return true
   124  		}
   125  		return false
   126  	})
   127  
   128  	// Withdraw 5 (should not run until after reserves / releases)
   129  	withdrawReady := make(chan error)
   130  	go func() {
   131  		amt = abi.NewTokenAmount(5)
   132  		_, err := s.fm.Withdraw(s.ctx, s.walletAddr, s.acctAddr, amt)
   133  		withdrawReady <- err
   134  	}()
   135  
   136  	reserveSentinels := make(chan cid.Cid)
   137  
   138  	// Reserve 3
   139  	go func() {
   140  		amt := abi.NewTokenAmount(3)
   141  		sentinelReserve3, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt)
   142  		require.NoError(t, err)
   143  		reserveSentinels <- sentinelReserve3
   144  	}()
   145  
   146  	// Reserve 5
   147  	go func() {
   148  		amt := abi.NewTokenAmount(5)
   149  		sentinelReserve5, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt)
   150  		require.NoError(t, err)
   151  		reserveSentinels <- sentinelReserve5
   152  	}()
   153  
   154  	// Release 2
   155  	go func() {
   156  		amt := abi.NewTokenAmount(2)
   157  		err = s.fm.Release(s.acctAddr, amt)
   158  		require.NoError(t, err)
   159  	}()
   160  
   161  	// Everything is queued up
   162  	<-queueReady
   163  
   164  	// Complete the "Reserve 10" message
   165  	s.mockApi.completeMsg(sentinelReserve10)
   166  	msg := s.mockApi.getSentMessage(sentinelReserve10)
   167  	checkAddMessageFields(t, msg, s.walletAddr, s.acctAddr, abi.NewTokenAmount(10))
   168  
   169  	// The other requests should now be combined and be submitted on-chain as
   170  	// a single message
   171  	rs1 := <-reserveSentinels
   172  	rs2 := <-reserveSentinels
   173  	require.Equal(t, rs1, rs2)
   174  
   175  	// Withdraw should not have been called yet, because reserve / release
   176  	// requests run first
   177  	select {
   178  	case <-withdrawReady:
   179  		require.Fail(t, "Withdraw should run after reserve / release")
   180  	default:
   181  	}
   182  
   183  	// Complete the message
   184  	s.mockApi.completeMsg(rs1)
   185  	msg = s.mockApi.getSentMessage(rs1)
   186  
   187  	// "Reserve 3" +3
   188  	// "Reserve 5" +5
   189  	// "Release 2" -2
   190  	// Result:      6
   191  	checkAddMessageFields(t, msg, s.walletAddr, s.acctAddr, abi.NewTokenAmount(6))
   192  
   193  	// Expect withdraw to fail because not enough available funds
   194  	err = <-withdrawReady
   195  	require.Error(t, err)
   196  }
   197  
   198  // TestFundManagerReserveByWallet verifies that reserve requests are grouped by wallet
   199  func TestFundManagerReserveByWallet(t *testing.T) {
   200  	s := setup(t)
   201  	defer s.fm.Stop()
   202  
   203  	walletAddrA, err := s.wllt.WalletNew(context.Background(), types.KTSecp256k1)
   204  	require.NoError(t, err)
   205  	walletAddrB, err := s.wllt.WalletNew(context.Background(), types.KTSecp256k1)
   206  	require.NoError(t, err)
   207  
   208  	// Wait until all the reservation requests are queued up
   209  	walletAQueuedUp := make(chan struct{})
   210  	queueReady := make(chan struct{})
   211  	fa := s.fm.getFundedAddress(s.acctAddr)
   212  	fa.onProcessStart(func() bool {
   213  		if len(fa.reservations) == 1 {
   214  			close(walletAQueuedUp)
   215  		}
   216  		if len(fa.reservations) == 3 {
   217  			close(queueReady)
   218  			return true
   219  		}
   220  		return false
   221  	})
   222  
   223  	type reserveResult struct {
   224  		ws  cid.Cid
   225  		err error
   226  	}
   227  	results := make(chan *reserveResult)
   228  
   229  	amtA1 := abi.NewTokenAmount(1)
   230  	go func() {
   231  		// Wallet A: Reserve 1
   232  		sentinelA1, err := s.fm.Reserve(s.ctx, walletAddrA, s.acctAddr, amtA1)
   233  		results <- &reserveResult{
   234  			ws:  sentinelA1,
   235  			err: err,
   236  		}
   237  	}()
   238  
   239  	amtB1 := abi.NewTokenAmount(2)
   240  	amtB2 := abi.NewTokenAmount(3)
   241  	go func() {
   242  		// Wait for reservation for wallet A to be queued up
   243  		<-walletAQueuedUp
   244  
   245  		// Wallet B: Reserve 2
   246  		go func() {
   247  			sentinelB1, err := s.fm.Reserve(s.ctx, walletAddrB, s.acctAddr, amtB1)
   248  			results <- &reserveResult{
   249  				ws:  sentinelB1,
   250  				err: err,
   251  			}
   252  		}()
   253  
   254  		// Wallet B: Reserve 3
   255  		sentinelB2, err := s.fm.Reserve(s.ctx, walletAddrB, s.acctAddr, amtB2)
   256  		results <- &reserveResult{
   257  			ws:  sentinelB2,
   258  			err: err,
   259  		}
   260  	}()
   261  
   262  	// All reservation requests are queued up
   263  	<-queueReady
   264  
   265  	resA := <-results
   266  	sentinelA1 := resA.ws
   267  
   268  	// Should send to wallet A
   269  	msg := s.mockApi.getSentMessage(sentinelA1)
   270  	checkAddMessageFields(t, msg, walletAddrA, s.acctAddr, amtA1)
   271  
   272  	// Complete wallet A message
   273  	s.mockApi.completeMsg(sentinelA1)
   274  
   275  	resB1 := <-results
   276  	resB2 := <-results
   277  	require.NoError(t, resB1.err)
   278  	require.NoError(t, resB2.err)
   279  	sentinelB1 := resB1.ws
   280  	sentinelB2 := resB2.ws
   281  
   282  	// Should send different message to wallet B
   283  	require.NotEqual(t, sentinelA1, sentinelB1)
   284  	// Should be single message combining amount 1 and 2
   285  	require.Equal(t, sentinelB1, sentinelB2)
   286  	msg = s.mockApi.getSentMessage(sentinelB1)
   287  	checkAddMessageFields(t, msg, walletAddrB, s.acctAddr, types.BigAdd(amtB1, amtB2))
   288  }
   289  
   290  // TestFundManagerWithdrawal verifies that as many withdraw operations as
   291  // possible are processed
   292  func TestFundManagerWithdrawalLimit(t *testing.T) {
   293  	s := setup(t)
   294  	defer s.fm.Stop()
   295  
   296  	// Reserve 10
   297  	amt := abi.NewTokenAmount(10)
   298  	sentinelReserve10, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt)
   299  	require.NoError(t, err)
   300  
   301  	// Complete the "Reserve 10" message
   302  	s.mockApi.completeMsg(sentinelReserve10)
   303  
   304  	// Release 10
   305  	err = s.fm.Release(s.acctAddr, amt)
   306  	require.NoError(t, err)
   307  
   308  	// Queue up withdraw requests
   309  	queueReady := make(chan struct{})
   310  	fa := s.fm.getFundedAddress(s.acctAddr)
   311  	withdrawalReqTotal := 3
   312  	withdrawalReqEnqueued := 0
   313  	withdrawalReqQueue := make(chan func(), withdrawalReqTotal)
   314  	fa.onProcessStart(func() bool {
   315  		// If a new withdrawal request was enqueued
   316  		if len(fa.withdrawals) > withdrawalReqEnqueued {
   317  			withdrawalReqEnqueued++
   318  
   319  			// Pop the next request and run it
   320  			select {
   321  			case fn := <-withdrawalReqQueue:
   322  				go fn()
   323  			default:
   324  			}
   325  		}
   326  		// Once all the requests have arrived, we're ready to process the queue
   327  		if withdrawalReqEnqueued == withdrawalReqTotal {
   328  			close(queueReady)
   329  			return true
   330  		}
   331  		return false
   332  	})
   333  
   334  	type withdrawResult struct {
   335  		reqIndex int
   336  		ws       cid.Cid
   337  		err      error
   338  	}
   339  	withdrawRes := make(chan *withdrawResult)
   340  
   341  	// Queue up three "Withdraw 5" requests
   342  	enqueuedCount := 0
   343  	for i := 0; i < withdrawalReqTotal; i++ {
   344  		withdrawalReqQueue <- func() {
   345  			idx := enqueuedCount
   346  			enqueuedCount++
   347  
   348  			amt := abi.NewTokenAmount(5)
   349  			ws, err := s.fm.Withdraw(s.ctx, s.walletAddr, s.acctAddr, amt)
   350  			withdrawRes <- &withdrawResult{reqIndex: idx, ws: ws, err: err}
   351  		}
   352  	}
   353  	// Start the first request
   354  	fn := <-withdrawalReqQueue
   355  	go fn()
   356  
   357  	// All withdrawal requests are queued up and ready to be processed
   358  	<-queueReady
   359  
   360  	// Organize results in request order
   361  	results := make([]*withdrawResult, withdrawalReqTotal)
   362  	for i := 0; i < 3; i++ {
   363  		res := <-withdrawRes
   364  		results[res.reqIndex] = res
   365  	}
   366  
   367  	// Available 10
   368  	// Withdraw 5
   369  	// Expect Success
   370  	require.NoError(t, results[0].err)
   371  	// Available 5
   372  	// Withdraw 5
   373  	// Expect Success
   374  	require.NoError(t, results[1].err)
   375  	// Available 0
   376  	// Withdraw 5
   377  	// Expect FAIL
   378  	require.Error(t, results[2].err)
   379  
   380  	// Expect withdrawal requests that fit under reserved amount to be combined
   381  	// into a single message on-chain
   382  	require.Equal(t, results[0].ws, results[1].ws)
   383  }
   384  
   385  // TestFundManagerWithdrawByWallet verifies that withdraw requests are grouped by wallet
   386  func TestFundManagerWithdrawByWallet(t *testing.T) {
   387  	s := setup(t)
   388  	defer s.fm.Stop()
   389  
   390  	walletAddrA, err := s.wllt.WalletNew(context.Background(), types.KTSecp256k1)
   391  	require.NoError(t, err)
   392  	walletAddrB, err := s.wllt.WalletNew(context.Background(), types.KTSecp256k1)
   393  	require.NoError(t, err)
   394  
   395  	// Reserve 10
   396  	reserveAmt := abi.NewTokenAmount(10)
   397  	sentinelReserve, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, reserveAmt)
   398  	require.NoError(t, err)
   399  	s.mockApi.completeMsg(sentinelReserve)
   400  
   401  	time.Sleep(10 * time.Millisecond)
   402  
   403  	// Release 10
   404  	err = s.fm.Release(s.acctAddr, reserveAmt)
   405  	require.NoError(t, err)
   406  
   407  	type withdrawResult struct {
   408  		ws  cid.Cid
   409  		err error
   410  	}
   411  	results := make(chan *withdrawResult)
   412  
   413  	// Wait until withdrawals are queued up
   414  	walletAQueuedUp := make(chan struct{})
   415  	queueReady := make(chan struct{})
   416  	withdrawalCount := 0
   417  	fa := s.fm.getFundedAddress(s.acctAddr)
   418  	fa.onProcessStart(func() bool {
   419  		if len(fa.withdrawals) == withdrawalCount {
   420  			return false
   421  		}
   422  		withdrawalCount = len(fa.withdrawals)
   423  
   424  		if withdrawalCount == 1 {
   425  			close(walletAQueuedUp)
   426  		} else if withdrawalCount == 3 {
   427  			close(queueReady)
   428  			return true
   429  		}
   430  		return false
   431  	})
   432  
   433  	amtA1 := abi.NewTokenAmount(1)
   434  	go func() {
   435  		// Wallet A: Withdraw 1
   436  		sentinelA1, err := s.fm.Withdraw(s.ctx, walletAddrA, s.acctAddr, amtA1)
   437  		results <- &withdrawResult{
   438  			ws:  sentinelA1,
   439  			err: err,
   440  		}
   441  	}()
   442  
   443  	amtB1 := abi.NewTokenAmount(2)
   444  	amtB2 := abi.NewTokenAmount(3)
   445  	go func() {
   446  		// Wait until withdraw for wallet A is queued up
   447  		<-walletAQueuedUp
   448  
   449  		// Wallet B: Withdraw 2
   450  		go func() {
   451  			sentinelB1, err := s.fm.Withdraw(s.ctx, walletAddrB, s.acctAddr, amtB1)
   452  			results <- &withdrawResult{
   453  				ws:  sentinelB1,
   454  				err: err,
   455  			}
   456  		}()
   457  
   458  		// Wallet B: Withdraw 3
   459  		sentinelB2, err := s.fm.Withdraw(s.ctx, walletAddrB, s.acctAddr, amtB2)
   460  		results <- &withdrawResult{
   461  			ws:  sentinelB2,
   462  			err: err,
   463  		}
   464  	}()
   465  
   466  	// Withdrawals are queued up
   467  	<-queueReady
   468  
   469  	// Should withdraw from wallet A first
   470  	resA1 := <-results
   471  	sentinelA1 := resA1.ws
   472  	msg := s.mockApi.getSentMessage(sentinelA1)
   473  	checkWithdrawMessageFields(t, msg, walletAddrA, s.acctAddr, amtA1)
   474  
   475  	// Complete wallet A message
   476  	s.mockApi.completeMsg(sentinelA1)
   477  
   478  	resB1 := <-results
   479  	resB2 := <-results
   480  	require.NoError(t, resB1.err)
   481  	require.NoError(t, resB2.err)
   482  	sentinelB1 := resB1.ws
   483  	sentinelB2 := resB2.ws
   484  
   485  	// Should send different message for wallet B from wallet A
   486  	require.NotEqual(t, sentinelA1, sentinelB1)
   487  	// Should be single message combining amount 1 and 2
   488  	require.Equal(t, sentinelB1, sentinelB2)
   489  	msg = s.mockApi.getSentMessage(sentinelB1)
   490  	checkWithdrawMessageFields(t, msg, walletAddrB, s.acctAddr, types.BigAdd(amtB1, amtB2))
   491  }
   492  
   493  // TestFundManagerRestart verifies that waiting for incomplete requests resumes
   494  // on restart
   495  func TestFundManagerRestart(t *testing.T) {
   496  	s := setup(t)
   497  	defer s.fm.Stop()
   498  
   499  	acctAddr2 := tutils.NewActorAddr(t, "addr2")
   500  
   501  	// Address 1: Reserve 10
   502  	amt := abi.NewTokenAmount(10)
   503  	sentinelAddr1, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt)
   504  	require.NoError(t, err)
   505  
   506  	msg := s.mockApi.getSentMessage(sentinelAddr1)
   507  	checkAddMessageFields(t, msg, s.walletAddr, s.acctAddr, amt)
   508  
   509  	// Address 2: Reserve 7
   510  	amt2 := abi.NewTokenAmount(7)
   511  	sentinelAddr2Res7, err := s.fm.Reserve(s.ctx, s.walletAddr, acctAddr2, amt2)
   512  	require.NoError(t, err)
   513  
   514  	msg2 := s.mockApi.getSentMessage(sentinelAddr2Res7)
   515  	checkAddMessageFields(t, msg2, s.walletAddr, acctAddr2, amt2)
   516  
   517  	// Complete "Address 1: Reserve 10"
   518  	s.mockApi.completeMsg(sentinelAddr1)
   519  
   520  	// Give the completed state a moment to be stored before restart
   521  	time.Sleep(time.Millisecond * 10)
   522  
   523  	// Restart
   524  	mockApiAfter := s.mockApi
   525  	fmAfter := newFundManager(mockApiAfter, s.ds)
   526  	err = fmAfter.Start()
   527  	require.NoError(t, err)
   528  
   529  	amt3 := abi.NewTokenAmount(9)
   530  	reserveSentinel := make(chan cid.Cid)
   531  	go func() {
   532  		// Address 2: Reserve 9
   533  		sentinel3, err := fmAfter.Reserve(s.ctx, s.walletAddr, acctAddr2, amt3)
   534  		require.NoError(t, err)
   535  		reserveSentinel <- sentinel3
   536  	}()
   537  
   538  	// Expect no message to be sent, because still waiting for previous
   539  	// message "Address 2: Reserve 7" to complete on-chain
   540  	select {
   541  	case <-reserveSentinel:
   542  		require.Fail(t, "Expected no message to be sent")
   543  	case <-time.After(10 * time.Millisecond):
   544  	}
   545  
   546  	// Complete "Address 2: Reserve 7"
   547  	mockApiAfter.completeMsg(sentinelAddr2Res7)
   548  
   549  	// Expect waiting message to now be sent
   550  	sentinel3 := <-reserveSentinel
   551  	msg3 := mockApiAfter.getSentMessage(sentinel3)
   552  	checkAddMessageFields(t, msg3, s.walletAddr, acctAddr2, amt3)
   553  }
   554  
   555  // TestFundManagerReleaseAfterPublish verifies that release is successful in
   556  // the following scenario:
   557  // 1. Deal A adds 5 to addr1:                     reserved  0 ->  5    available  0 ->  5
   558  // 2. Deal B adds 7 to addr1:                     reserved  5 -> 12    available  5 -> 12
   559  // 3. Deal B completes, reducing addr1 by 7:      reserved       12    available 12 ->  5
   560  // 4. Deal A releases 5 from addr1:               reserved 12 ->  7    available        5
   561  func TestFundManagerReleaseAfterPublish(t *testing.T) {
   562  	s := setup(t)
   563  	defer s.fm.Stop()
   564  
   565  	// Deal A: Reserve 5
   566  	// balance:  0 -> 5
   567  	// reserved: 0 -> 5
   568  	amt := abi.NewTokenAmount(5)
   569  	sentinel, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt)
   570  	require.NoError(t, err)
   571  	s.mockApi.completeMsg(sentinel)
   572  
   573  	// Deal B: Reserve 7
   574  	// balance:  5 -> 12
   575  	// reserved: 5 -> 12
   576  	amt = abi.NewTokenAmount(7)
   577  	sentinel, err = s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt)
   578  	require.NoError(t, err)
   579  	s.mockApi.completeMsg(sentinel)
   580  
   581  	// Deal B: Publish (removes Deal B amount from balance)
   582  	// balance:  12 -> 5
   583  	// reserved: 12
   584  	amt = abi.NewTokenAmount(7)
   585  	s.mockApi.publish(s.acctAddr, amt)
   586  
   587  	// Deal A: Release 5
   588  	// balance:  5
   589  	// reserved: 12 -> 7
   590  	amt = abi.NewTokenAmount(5)
   591  	err = s.fm.Release(s.acctAddr, amt)
   592  	require.NoError(t, err)
   593  
   594  	// Deal B: Release 7
   595  	// balance:  5
   596  	// reserved: 12 -> 7
   597  	amt = abi.NewTokenAmount(5)
   598  	err = s.fm.Release(s.acctAddr, amt)
   599  	require.NoError(t, err)
   600  }
   601  
   602  type scaffold struct {
   603  	ctx        context.Context
   604  	ds         *ds_sync.MutexDatastore
   605  	wllt       *wallet.LocalWallet
   606  	walletAddr address.Address
   607  	acctAddr   address.Address
   608  	mockApi    *mockFundManagerAPI
   609  	fm         *FundManager
   610  }
   611  
   612  func setup(t *testing.T) *scaffold {
   613  	ctx := context.Background()
   614  
   615  	wllt, err := wallet.NewWallet(wallet.NewMemKeyStore())
   616  	if err != nil {
   617  		t.Fatal(err)
   618  	}
   619  
   620  	walletAddr, err := wllt.WalletNew(context.Background(), types.KTSecp256k1)
   621  	if err != nil {
   622  		t.Fatal(err)
   623  	}
   624  
   625  	acctAddr := tutils.NewActorAddr(t, "addr")
   626  
   627  	mockApi := newMockFundManagerAPI(walletAddr)
   628  	dstore := ds_sync.MutexWrap(ds.NewMapDatastore())
   629  	fm := newFundManager(mockApi, dstore)
   630  	return &scaffold{
   631  		ctx:        ctx,
   632  		ds:         dstore,
   633  		wllt:       wllt,
   634  		walletAddr: walletAddr,
   635  		acctAddr:   acctAddr,
   636  		mockApi:    mockApi,
   637  		fm:         fm,
   638  	}
   639  }
   640  
   641  func checkAddMessageFields(t *testing.T, msg *types.Message, from address.Address, to address.Address, amt abi.TokenAmount) {
   642  	require.Equal(t, from, msg.From)
   643  	require.Equal(t, market.Address, msg.To)
   644  	require.Equal(t, amt, msg.Value)
   645  
   646  	var paramsTo address.Address
   647  	err := paramsTo.UnmarshalCBOR(bytes.NewReader(msg.Params))
   648  	require.NoError(t, err)
   649  	require.Equal(t, to, paramsTo)
   650  }
   651  
   652  func checkWithdrawMessageFields(t *testing.T, msg *types.Message, from address.Address, addr address.Address, amt abi.TokenAmount) {
   653  	require.Equal(t, from, msg.From)
   654  	require.Equal(t, market.Address, msg.To)
   655  	require.Equal(t, abi.NewTokenAmount(0), msg.Value)
   656  
   657  	var params market.WithdrawBalanceParams
   658  	err := params.UnmarshalCBOR(bytes.NewReader(msg.Params))
   659  	require.NoError(t, err)
   660  	require.Equal(t, addr, params.ProviderOrClientAddress)
   661  	require.Equal(t, amt, params.Amount)
   662  }
   663  
   664  type sentMsg struct {
   665  	msg   *types.SignedMessage
   666  	ready chan struct{}
   667  }
   668  
   669  type mockFundManagerAPI struct {
   670  	wallet address.Address
   671  
   672  	lk            sync.Mutex
   673  	escrow        map[address.Address]abi.TokenAmount
   674  	sentMsgs      map[cid.Cid]*sentMsg
   675  	completedMsgs map[cid.Cid]struct{}
   676  	waitingFor    map[cid.Cid]chan struct{}
   677  }
   678  
   679  func newMockFundManagerAPI(wallet address.Address) *mockFundManagerAPI {
   680  	return &mockFundManagerAPI{
   681  		wallet:        wallet,
   682  		escrow:        make(map[address.Address]abi.TokenAmount),
   683  		sentMsgs:      make(map[cid.Cid]*sentMsg),
   684  		completedMsgs: make(map[cid.Cid]struct{}),
   685  		waitingFor:    make(map[cid.Cid]chan struct{}),
   686  	}
   687  }
   688  
   689  func (mapi *mockFundManagerAPI) MpoolPushMessage(ctx context.Context, message *types.Message, spec *api.MessageSendSpec) (*types.SignedMessage, error) {
   690  	mapi.lk.Lock()
   691  	defer mapi.lk.Unlock()
   692  
   693  	smsg := &types.SignedMessage{Message: *message}
   694  	mapi.sentMsgs[smsg.Cid()] = &sentMsg{msg: smsg, ready: make(chan struct{})}
   695  
   696  	return smsg, nil
   697  }
   698  
   699  func (mapi *mockFundManagerAPI) getSentMessage(c cid.Cid) *types.Message {
   700  	mapi.lk.Lock()
   701  	defer mapi.lk.Unlock()
   702  
   703  	for i := 0; i < 1000; i++ {
   704  		if pending, ok := mapi.sentMsgs[c]; ok {
   705  			return &pending.msg.Message
   706  		}
   707  		time.Sleep(time.Millisecond)
   708  	}
   709  	panic("expected message to be sent")
   710  }
   711  
   712  func (mapi *mockFundManagerAPI) messageCount() int {
   713  	mapi.lk.Lock()
   714  	defer mapi.lk.Unlock()
   715  
   716  	return len(mapi.sentMsgs)
   717  }
   718  
   719  func (mapi *mockFundManagerAPI) completeMsg(msgCid cid.Cid) {
   720  	mapi.lk.Lock()
   721  
   722  	pmsg, ok := mapi.sentMsgs[msgCid]
   723  	if ok {
   724  		if pmsg.msg.Message.Method == market.Methods.AddBalance {
   725  			var escrowAcct address.Address
   726  			err := escrowAcct.UnmarshalCBOR(bytes.NewReader(pmsg.msg.Message.Params))
   727  			if err != nil {
   728  				panic(err)
   729  			}
   730  
   731  			escrow := mapi.getEscrow(escrowAcct)
   732  			before := escrow
   733  			escrow = types.BigAdd(escrow, pmsg.msg.Message.Value)
   734  			mapi.escrow[escrowAcct] = escrow
   735  			log.Debugf("%s:   escrow %d -> %d", escrowAcct, before, escrow)
   736  		} else {
   737  			var params market.WithdrawBalanceParams
   738  			err := params.UnmarshalCBOR(bytes.NewReader(pmsg.msg.Message.Params))
   739  			if err != nil {
   740  				panic(err)
   741  			}
   742  			escrowAcct := params.ProviderOrClientAddress
   743  
   744  			escrow := mapi.getEscrow(escrowAcct)
   745  			before := escrow
   746  			escrow = types.BigSub(escrow, params.Amount)
   747  			mapi.escrow[escrowAcct] = escrow
   748  			log.Debugf("%s:   escrow %d -> %d", escrowAcct, before, escrow)
   749  		}
   750  	}
   751  
   752  	mapi.completedMsgs[msgCid] = struct{}{}
   753  
   754  	ready, ok := mapi.waitingFor[msgCid]
   755  
   756  	mapi.lk.Unlock()
   757  
   758  	if ok {
   759  		close(ready)
   760  	}
   761  }
   762  
   763  func (mapi *mockFundManagerAPI) StateMarketBalance(ctx context.Context, a address.Address, key types.TipSetKey) (api.MarketBalance, error) {
   764  	mapi.lk.Lock()
   765  	defer mapi.lk.Unlock()
   766  
   767  	return api.MarketBalance{
   768  		Locked: abi.NewTokenAmount(0),
   769  		Escrow: mapi.getEscrow(a),
   770  	}, nil
   771  }
   772  
   773  func (mapi *mockFundManagerAPI) getEscrow(a address.Address) abi.TokenAmount {
   774  	escrow := mapi.escrow[a]
   775  	if escrow.Nil() {
   776  		return abi.NewTokenAmount(0)
   777  	}
   778  	return escrow
   779  }
   780  
   781  func (mapi *mockFundManagerAPI) publish(addr address.Address, amt abi.TokenAmount) {
   782  	mapi.lk.Lock()
   783  	defer mapi.lk.Unlock()
   784  
   785  	escrow := mapi.escrow[addr]
   786  	if escrow.Nil() {
   787  		return
   788  	}
   789  	escrow = types.BigSub(escrow, amt)
   790  	if escrow.LessThan(abi.NewTokenAmount(0)) {
   791  		escrow = abi.NewTokenAmount(0)
   792  	}
   793  	mapi.escrow[addr] = escrow
   794  }
   795  
   796  func (mapi *mockFundManagerAPI) StateWaitMsg(ctx context.Context, c cid.Cid, confidence uint64, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) {
   797  	res := &api.MsgLookup{
   798  		Message: c,
   799  		Receipt: types.MessageReceipt{
   800  			ExitCode: 0,
   801  			Return:   nil,
   802  		},
   803  	}
   804  	ready := make(chan struct{})
   805  
   806  	mapi.lk.Lock()
   807  	_, ok := mapi.completedMsgs[c]
   808  	if !ok {
   809  		mapi.waitingFor[c] = ready
   810  	}
   811  	mapi.lk.Unlock()
   812  
   813  	if !ok {
   814  		select {
   815  		case <-ctx.Done():
   816  		case <-ready:
   817  		}
   818  	}
   819  	return res, nil
   820  }