diff --git a/cmd/lit-af/dlccmds.go b/cmd/lit-af/dlccmds.go index 943806efd..e9201d124 100644 --- a/cmd/lit-af/dlccmds.go +++ b/cmd/lit-af/dlccmds.go @@ -117,6 +117,9 @@ var contractCommand = &Command{ fmt.Sprintf("%-20s %s", lnutil.White("setcointype"), "Sets the cointype of a contract"), + fmt.Sprintf("%-20s %s", + lnutil.White("setfeeperbyte"), + "Sets the fee per byte for a contract"), fmt.Sprintf("%-20s %s", lnutil.White("offer"), "Offer a draft contract to one of your peers"), @@ -289,6 +292,20 @@ var setContractCoinTypeCommand = &Command{ ), ShortDescription: "Sets the coin type to use for the contract\n", } +var setContractFeePerByteCommand = &Command{ + Format: fmt.Sprintf("%s%s\n", lnutil.White("dlc contract setfeeperbyte"), + lnutil.ReqColor("cid", "feeperbyte")), + Description: fmt.Sprintf("%s\n%s\n%s\n", + "Sets the fee per byte to use for the contract", + fmt.Sprintf("%-10s %s", + lnutil.White("cid"), + "The ID of the contract"), + fmt.Sprintf("%-10s %s", + lnutil.White("cointype"), + "The fee per byte in satoshi to use for the contract"), + ), + ShortDescription: "Sets the fee per byte in satoshi to use for the contract\n", +} var declineContractCommand = &Command{ Format: fmt.Sprintf("%s%s\n", lnutil.White("dlc contract decline"), lnutil.ReqColor("cid")), @@ -504,6 +521,10 @@ func (lc *litAfClient) DlcContract(textArgs []string) error { return lc.DlcSetContractCoinType(textArgs) } + if cmd == "setfeeperbyte" { + return lc.DlcSetContractFeePerByte(textArgs) + } + if cmd == "offer" { return lc.DlcOfferContract(textArgs) } @@ -803,6 +824,40 @@ func (lc *litAfClient) DlcSetContractCoinType(textArgs []string) error { return nil } + +func (lc *litAfClient) DlcSetContractFeePerByte(textArgs []string) error { + stopEx, err := CheckHelpCommand(setContractFeePerByteCommand, textArgs, 2) + if err != nil || stopEx { + return err + } + + args := new(litrpc.SetContractFeePerByteArgs) + reply := new(litrpc.SetContractFeePerByteReply) + + cIdx, err := strconv.ParseUint(textArgs[0], 10, 64) + if err != nil { + return err + } + feeperbyte, err := strconv.ParseUint(textArgs[1], 10, 64) + if err != nil { + return err + } + + args.CIdx = cIdx + args.FeePerByte = uint32(feeperbyte) + + err = lc.Call("LitRPC.SetContractFeePerByte", args, reply) + if err != nil { + return err + } + + fmt.Fprint(color.Output, "Fee per byte set successfully\n") + + return nil +} + + + func (lc *litAfClient) DlcSetContractDivision(textArgs []string) error { stopEx, err := CheckHelpCommand(setContractDivisionCommand, textArgs, 3) if err != nil || stopEx { @@ -969,6 +1024,8 @@ func PrintContract(c *lnutil.DlcContract) { lnutil.White("Funded by peer"), c.TheirFundingAmount) fmt.Fprintf(color.Output, "%-30s : %d\n", lnutil.White("Coin type"), c.CoinType) + fmt.Fprintf(color.Output, "%-30s : %d\n", + lnutil.White("Fee per byte"), c.FeePerByte) peer := "None" if c.PeerIdx > 0 { diff --git a/dlc/contract.go b/dlc/contract.go index cc786cbdf..86ef2674f 100644 --- a/dlc/contract.go +++ b/dlc/contract.go @@ -7,6 +7,7 @@ import ( ) const COINTYPE_NOT_SET = ^uint32(0) // Max Uint +const FEEPERBYTE_NOT_SET = ^uint32(0) // Max Uint // AddContract starts a new draft contract func (mgr *DlcManager) AddContract() (*lnutil.DlcContract, error) { @@ -15,6 +16,7 @@ func (mgr *DlcManager) AddContract() (*lnutil.DlcContract, error) { c := new(lnutil.DlcContract) c.Status = lnutil.ContractStatusDraft c.CoinType = COINTYPE_NOT_SET + c.FeePerByte = FEEPERBYTE_NOT_SET err = mgr.SaveContract(c) if err != nil { return nil, err @@ -244,3 +246,23 @@ func (mgr *DlcManager) SetContractCoinType(cIdx uint64, cointype uint32) error { return nil } + + +//SetContractFeePerByte sets the fee per byte for a particular contract +func (mgr *DlcManager) SetContractFeePerByte(cIdx uint64, feeperbyte uint32) error { + c, err := mgr.LoadContract(cIdx) + if err != nil { + return err + } + + if c.Status != lnutil.ContractStatusDraft { + return fmt.Errorf("You cannot change or set the fee per byte unless" + + " the contract is in Draft state") + } + + c.FeePerByte = feeperbyte + + mgr.SaveContract(c) + + return nil +} diff --git a/litrpc/dlccmds.go b/litrpc/dlccmds.go index bb6d84cb2..0c9e6ab50 100644 --- a/litrpc/dlccmds.go +++ b/litrpc/dlccmds.go @@ -2,6 +2,7 @@ package litrpc import ( "encoding/hex" + "fmt" "github.com/mit-dci/lit/dlc" "github.com/mit-dci/lit/lnutil" @@ -314,6 +315,69 @@ func (r *LitRPC) SetContractCoinType(args SetContractCoinTypeArgs, return nil } + +type SetContractFeePerByteArgs struct { + CIdx uint64 + FeePerByte uint32 +} + +type SetContractFeePerByteReply struct { + Success bool +} + +// SetContractFeePerByte sets the fee per byte for the contract. +func (r *LitRPC) SetContractFeePerByte(args SetContractFeePerByteArgs, + reply *SetContractFeePerByteReply) error { + var err error + + err = r.Node.DlcManager.SetContractFeePerByte(args.CIdx, args.FeePerByte) + if err != nil { + return err + } + + reply.Success = true + return nil +} + +//---------------------------------------------------------- + +type GetContractDivisionArgs struct { + CIdx uint64 + OracleValue int64 +} + +type GetContractDivisionReply struct { + ValueOurs int64 +} + +// GetContractDivision +func (r *LitRPC) GetContractDivision(args GetContractDivisionArgs, + reply *GetContractDivisionReply) error { + + //err = r.Node.DlcManager.GetContractDivision(args.CIdx, args.OracleValue) + + c, err1 := r.Node.DlcManager.LoadContract(args.CIdx) + if err1 != nil { + fmt.Errorf("GetContractDivision(): LoadContract err %s\n", err1.Error()) + return err1 + } + + + d, err2 := c.GetDivision(args.OracleValue) + if err2 != nil { + fmt.Errorf("GetContractDivision(): c.GetDivision err %s\n", err2.Error()) + return err2 + } + reply.ValueOurs = d.ValueOurs + + return nil +} + + +//----------------------------------------------------------- + + + type OfferContractArgs struct { CIdx uint64 PeerIdx uint32 diff --git a/litrpc/lndcrpcclient.go b/litrpc/lndcrpcclient.go index 7a508ee0b..b5372615d 100644 --- a/litrpc/lndcrpcclient.go +++ b/litrpc/lndcrpcclient.go @@ -28,9 +28,11 @@ type LndcRpcClient struct { requestNonce uint64 requestNonceMtx sync.Mutex responseChannelMtx sync.Mutex - responseChannels map[uint64]chan lnutil.RemoteControlRpcResponseMsg + responseChannels map[uint64]chan *lnutil.RemoteControlRpcResponseMsg key *koblitz.PrivateKey conMtx sync.Mutex + + chunksOfMsg map[int64]*lnutil.ChunkMsg } // LndcRpcCanConnectLocally checks if we can connect to lit using the normal @@ -116,9 +118,12 @@ func NewLndcRpcClient(address string, key *koblitz.PrivateKey) (*LndcRpcClient, var err error cli := new(LndcRpcClient) + + cli.chunksOfMsg = make(map[int64]*lnutil.ChunkMsg) + // Create a map of chan objects to receive returned responses on. These channels // are sent to from the ReceiveLoop, and awaited in the Call method. - cli.responseChannels = make(map[uint64]chan lnutil.RemoteControlRpcResponseMsg) + cli.responseChannels = make(map[uint64]chan *lnutil.RemoteControlRpcResponseMsg) //Parse the address we're connecting to who, where := lnutil.ParseAdrString(address) @@ -158,7 +163,7 @@ func (cli *LndcRpcClient) Call(serviceMethod string, args interface{}, reply int // Create the channel to receive the reply on cli.responseChannelMtx.Lock() - cli.responseChannels[nonce] = make(chan lnutil.RemoteControlRpcResponseMsg) + cli.responseChannels[nonce] = make(chan *lnutil.RemoteControlRpcResponseMsg) cli.responseChannelMtx.Unlock() // Send the message in a goroutine @@ -221,6 +226,35 @@ func (cli *LndcRpcClient) ReceiveLoop() { return } msg = msg[:n] + + if msg[0] == lnutil.MSGID_CHUNKS_BEGIN { + + beginChunksMsg, _ := lnutil.NewChunksBeginMsgFromBytes(msg, 0) + + msg_tmp := new(lnutil.ChunkMsg) + msg_tmp.TimeStamp = beginChunksMsg.TimeStamp + cli.chunksOfMsg[beginChunksMsg.TimeStamp] = msg_tmp + + continue + } + + if msg[0] == lnutil.MSGID_CHUNK_BODY { + + chunkMsg, _ := lnutil.NewChunkMsgFromBytes(msg, 0) + cli.chunksOfMsg[chunkMsg.TimeStamp].Data = append(cli.chunksOfMsg[chunkMsg.TimeStamp].Data, chunkMsg.Data...) + + continue + } + + if msg[0] == lnutil.MSGID_CHUNKS_END { + + endChunksMsg, _ := lnutil.NewChunksBeginMsgFromBytes(msg, 0) + msg = cli.chunksOfMsg[endChunksMsg.TimeStamp].Data + + } + + + // We only care about RPC responses (for now) if msg[0] == lnutil.MSGID_REMOTE_RPCRESPONSE { // Parse the received message @@ -239,7 +273,7 @@ func (cli *LndcRpcClient) ReceiveLoop() { // reply and therefore, it could have not blocked and just // ignore the return value. select { - case responseChan <- response: + case responseChan <- &response: default: } diff --git a/lndc/conn.go b/lndc/conn.go index 01c1f189c..afc786162 100644 --- a/lndc/conn.go +++ b/lndc/conn.go @@ -7,6 +7,7 @@ import ( "math" "net" "time" + "encoding/binary" "github.com/mit-dci/lit/crypto/koblitz" "github.com/mit-dci/lit/lnutil" @@ -151,27 +152,62 @@ func (c *Conn) Write(b []byte) (n int, err error) { // If we need to split the message into fragments, then we'll write // chunks which maximize usage of the available payload. - chunkSize := math.MaxUint16 + chunkSize := math.MaxUint16 - 1000 + + lenb := int(len(b)) + curts := (time.Now().UnixNano()) + + beginChunksMsgBodyBytes := new(bytes.Buffer) + beginChunksMsgBodyBytes.WriteByte(lnutil.MSGID_CHUNKS_BEGIN) + binary.Write(beginChunksMsgBodyBytes, binary.BigEndian, curts) + + // Start saving chunks + chunk_err := c.noise.WriteMessage(c.conn, beginChunksMsgBodyBytes.Bytes()) + if chunk_err != nil { + return 0, chunk_err + } - bytesToWrite := len(b) bytesWritten := 0 + bytesToWrite := lenb + for bytesWritten < bytesToWrite { // If we're on the last chunk, then truncate the chunk size as // necessary to avoid an out-of-bounds array memory access. if bytesWritten+chunkSize > len(b) { - chunkSize = len(b) - bytesWritten + chunkSize = lenb - bytesWritten } // Slice off the next chunk to be written based on our running // counter and next chunk size. chunk := b[bytesWritten : bytesWritten+chunkSize] - if err := c.noise.WriteMessage(c.conn, chunk); err != nil { - return bytesWritten, err + + // Wrap chunk in a MSGID_CHUNK_BODY message + chunkMsgBodyBytes := new(bytes.Buffer) + chunkMsgBodyBytes.WriteByte(lnutil.MSGID_CHUNK_BODY) + binary.Write(chunkMsgBodyBytes, binary.BigEndian, curts) + binary.Write(chunkMsgBodyBytes, binary.BigEndian, int32(chunkSize)) + chunkMsgBodyBytes.Write(chunk) + + + chunk_err = c.noise.WriteMessage(c.conn, chunkMsgBodyBytes.Bytes()) + if chunk_err != nil { + return 0, chunk_err } bytesWritten += len(chunk) } + // Actually send a message (unwrap and send) + endChunksMsgBodyBytes := new(bytes.Buffer) + endChunksMsgBodyBytes.WriteByte(lnutil.MSGID_CHUNKS_END) + binary.Write(endChunksMsgBodyBytes, binary.BigEndian, curts) + + chunk_err = c.noise.WriteMessage(c.conn, endChunksMsgBodyBytes.Bytes()) + if chunk_err != nil { + return 0, chunk_err + } + + return bytesWritten, nil } diff --git a/lnp2p/msgproc.go b/lnp2p/msgproc.go index 3897a9580..3df578fe8 100644 --- a/lnp2p/msgproc.go +++ b/lnp2p/msgproc.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/mit-dci/lit/logging" "sync" + "github.com/mit-dci/lit/lnutil" ) // ParseFuncType is the type of a Message parser function. @@ -28,6 +29,8 @@ type MessageProcessor struct { // TODO Evaluate if this mutex is even necessary? active bool actmtx *sync.Mutex + + ChunksOfMsg map[int64]*lnutil.ChunkMsg } // NewMessageProcessor processes messages coming in from over the network. @@ -36,6 +39,7 @@ func NewMessageProcessor() MessageProcessor { handlers: [256]*messagehandler{}, active: false, actmtx: &sync.Mutex{}, + ChunksOfMsg: make(map[int64]*lnutil.ChunkMsg), } } @@ -77,6 +81,43 @@ func (mp *MessageProcessor) HandleMessage(peer *Peer, buf []byte) error { // First see if we have handlers defined for this message type. mtype := buf[0] + + if mtype == 0xB2{ + + msg, _ := lnutil.NewChunksBeginMsgFromBytes(buf, peer.GetIdx()) + + chunk_msg := new(lnutil.ChunkMsg) + chunk_msg.TimeStamp = msg.TimeStamp + + mp.ChunksOfMsg[msg.TimeStamp] = chunk_msg + + return nil + + } + + if mtype == 0xB3{ + + msg, _ := lnutil.NewChunkMsgFromBytes(buf, peer.GetIdx()) + mp.ChunksOfMsg[msg.TimeStamp].Data = append(mp.ChunksOfMsg[msg.TimeStamp].Data, msg.Data...) + + return nil + + } + + if mtype == 0xB4{ + + msg, _ := lnutil.NewChunksEndMsgFromBytes(buf, peer.GetIdx()) + + buf = mp.ChunksOfMsg[msg.TimeStamp].Data + mtype = buf[0] + + delete(mp.ChunksOfMsg, msg.TimeStamp) + + } + + + + h := mp.handlers[mtype] if h == nil { return fmt.Errorf("no handler found for messasge of type %x", mtype) diff --git a/lnp2p/peermgr.go b/lnp2p/peermgr.go index f367ddd83..068d44fd6 100644 --- a/lnp2p/peermgr.go +++ b/lnp2p/peermgr.go @@ -7,6 +7,7 @@ import ( "net" "sync" "time" + "math" "github.com/mit-dci/lit/btcutil/hdkeychain" "github.com/mit-dci/lit/crypto/koblitz" @@ -87,6 +88,25 @@ func NewPeerManager(rootkey *hdkeychain.ExtendedKey, pdb lncore.LitPeerStorage, mtx: &sync.Mutex{}, } + + // Clear ChunksOfMsg in case of incomplete chunks transmittion. + // Try to clean the map every 5 minutes. Therefore message have to + // be transmitted within a 5 minutes. + go func(){ + + for { + + time.Sleep(5 * time.Minute) + + for k := range pm.mproc.ChunksOfMsg { + tdelta := time.Now().UnixNano() - k + if tdelta > 3*int64(math.Pow10(11)) { + delete(pm.mproc.ChunksOfMsg, k) + } + } + } + }() + return pm, nil } diff --git a/lnutil/dlclib.go b/lnutil/dlclib.go index a45c6724a..826ceca3b 100644 --- a/lnutil/dlclib.go +++ b/lnutil/dlclib.go @@ -6,9 +6,9 @@ import ( "encoding/binary" "fmt" "math/big" + "errors" "github.com/mit-dci/lit/btcutil/chaincfg/chainhash" - "github.com/mit-dci/lit/consts" "github.com/mit-dci/lit/crypto/koblitz" "github.com/mit-dci/lit/logging" "github.com/mit-dci/lit/wire" @@ -48,6 +48,8 @@ type DlcContract struct { PeerIdx uint32 // Coin type CoinType uint32 + // Fee per byte + FeePerByte uint32 // Pub keys of the oracle and the R point used in the contract OracleA, OracleR [33]byte // The time we expect the oracle to publish @@ -125,6 +127,14 @@ func DlcContractFromBytes(b []byte) (*DlcContract, error) { return nil, err } c.CoinType = uint32(coinType) + + feePerByte, err := wire.ReadVarInt(buf, 0) + if err != nil { + logging.Errorf("Error while deserializing varint for feePerByte: %s", err.Error()) + return nil, err + } + c.FeePerByte = uint32(feePerByte) + c.OracleTimestamp, err = wire.ReadVarInt(buf, 0) if err != nil { return nil, err @@ -245,6 +255,7 @@ func (self *DlcContract) Bytes() []byte { buf.Write(self.OracleR[:]) wire.WriteVarInt(&buf, 0, uint64(self.PeerIdx)) wire.WriteVarInt(&buf, 0, uint64(self.CoinType)) + wire.WriteVarInt(&buf, 0, uint64(self.FeePerByte)) wire.WriteVarInt(&buf, 0, uint64(self.OracleTimestamp)) wire.WriteVarInt(&buf, 0, uint64(self.OurFundingAmount)) wire.WriteVarInt(&buf, 0, uint64(self.TheirFundingAmount)) @@ -441,37 +452,108 @@ func computePubKey(pubA, pubR [33]byte, msg []byte) ([33]byte, error) { func SettlementTx(c *DlcContract, d DlcContractDivision, ours bool) (*wire.MsgTx, error) { + + + // Maximum possible size of transaction here is + // Version 4 bytes + LockTime 4 bytes + Serialized varint size for the + // number of transaction inputs and outputs. + // n := 8 + VarIntSerializeSize(uint64(len(msg.TxIn))) + + // VarIntSerializeSize(uint64(len(msg.TxOut))) + + // Plus Witness Data 218 + // Plus Single input 41 + // Plus Their output 43 + // Plus Our output 31 + // Plus 2 for all wittness transactions + // Total max size of tx here is: 4 + 4 + 1 + 1 + 2 + 218 + 41 + 43 + 31 = 345 + // Vsize: ( (345 - 218 - 2) * 3 + 345 ) / 4 = 180 + + maxVsize := 180 + tx := wire.NewMsgTx() // set version 2, for op_csv tx.Version = 2 tx.AddTxIn(wire.NewTxIn(&c.FundingOutpoint, nil, nil)) - totalFee := int64(consts.DlcSettlementTxFee) // TODO: Calculate - feeEach := int64(float64(totalFee) / float64(2)) + + totalFee := uint32(maxVsize * int(c.FeePerByte)) + + feeEach := uint32(totalFee / uint32(2)) feeOurs := feeEach feeTheirs := feeEach + + totalContractValue := c.TheirFundingAmount + c.OurFundingAmount + valueOurs := d.ValueOurs + valueTheirs := totalContractValue - d.ValueOurs + + if totalContractValue < int64(totalFee) { + return nil, errors.New("totalContractValue < totalFee") + } + + + vsize :=uint32(maxVsize) + // We don't have enough to pay for a fee. We get 0, our contract partner // pays the rest of the fee - if valueOurs < feeEach { - feeOurs = valueOurs - valueOurs = 0 - } else { - valueOurs = d.ValueOurs - feeOurs + if valueOurs < int64(feeOurs) { + + // Just recalculate totalFee, feeOurs, feeTheirs to exclude one of the output. + if ours { + + // exclude wire.NewTxOut from size (i.e 31) + vsize = uint32(150) + }else{ + // exclude DlcOutput from size (i.e 43) + vsize = uint32(137) + + } + totalFee = vsize * uint32(c.FeePerByte) + + feeEach = uint32(float64(totalFee) / float64(2)) + feeOurs = feeEach + feeTheirs = feeEach + + if valueOurs == 0 { // Also if we win 0, our contract partner pays the totalFee + feeTheirs = totalFee + }else{ + + feeTheirs += uint32(valueOurs) // Also if we win less that the fees, our prize goes + // to a counterparty to increase his fee for a tx. + valueOurs = 0 + + } } - totalContractValue := c.TheirFundingAmount + c.OurFundingAmount - valueTheirs := totalContractValue - d.ValueOurs - if valueTheirs < feeEach { - feeTheirs = valueTheirs - valueTheirs = 0 - feeOurs = totalFee - feeTheirs - valueOurs = d.ValueOurs - feeOurs - } else { - valueTheirs -= feeTheirs + // Due to check above it is impossible (valueTheirs < feeTheirs) and + // (valueOurs < feeOurs) are satisfied at the same time. + if valueTheirs < int64(feeTheirs) { + if ours { + vsize = uint32(137) + }else{ + vsize = uint32(150) + } + totalFee = vsize * c.FeePerByte + + + feeEach = uint32(float64(totalFee) / float64(2)) + feeOurs = feeEach + feeTheirs = feeEach + + if valueTheirs == 0 { + feeOurs = totalFee + }else{ + feeOurs += uint32(valueTheirs) + valueTheirs = 0 + + } } + + valueOurs -= int64(feeOurs) + valueTheirs -= int64(feeTheirs) + var buf bytes.Buffer binary.Write(&buf, binary.BigEndian, uint64(0)) binary.Write(&buf, binary.BigEndian, uint64(0)) diff --git a/lnutil/msglib.go b/lnutil/msglib.go index ae4599aa2..f7d1b9fd3 100644 --- a/lnutil/msglib.go +++ b/lnutil/msglib.go @@ -73,6 +73,11 @@ const ( MSGID_REMOTE_RPCREQUEST = 0xB0 // Contains an RPC request from a remote peer MSGID_REMOTE_RPCRESPONSE = 0xB1 // Contains an RPC response to send to a remote peer + MSGID_CHUNKS_BEGIN uint8 = 0xB2 + MSGID_CHUNK_BODY uint8 = 0xB3 + MSGID_CHUNKS_END uint8 = 0xB4 + + DIGEST_TYPE_SHA256 = 0x00 DIGEST_TYPE_RIPEMD160 = 0x01 ) @@ -2412,3 +2417,121 @@ func (msg RemoteControlRpcResponseMsg) Peer() uint32 { func (msg RemoteControlRpcResponseMsg) MsgType() uint8 { return MSGID_REMOTE_RPCRESPONSE } + + +// For chunked messages + +type BeginChunksMsg struct { + PeerIdx uint32 + TimeStamp int64 +} + +func NewChunksBeginMsgFromBytes(b []byte, peerIdx uint32) (BeginChunksMsg, error) { + + msg := new(BeginChunksMsg) + buf := bytes.NewBuffer(b[1:]) // get rid of messageType + + msg.PeerIdx = peerIdx + binary.Read(buf, binary.BigEndian, &msg.TimeStamp) + + return *msg, nil +} + +func (msg BeginChunksMsg) Bytes() []byte { + var buf bytes.Buffer + + buf.WriteByte(msg.MsgType()) + binary.Write(&buf, binary.BigEndian, msg.TimeStamp) + + return buf.Bytes() +} + +func (msg BeginChunksMsg) Peer() uint32 { + return msg.PeerIdx +} + +func (msg BeginChunksMsg) MsgType() uint8 { + return MSGID_CHUNKS_BEGIN +} + + +type ChunkMsg struct { + PeerIdx uint32 + TimeStamp int64 + ChunkSize int32 + Data []byte +} + + +func NewChunkMsgFromBytes(b []byte, peerIdx uint32) (ChunkMsg, error) { + + msg := new(ChunkMsg) + + buf := bytes.NewBuffer(b[1:]) // get rid of messageType + + msg.PeerIdx = peerIdx + binary.Read(buf, binary.BigEndian, &msg.TimeStamp) + binary.Read(buf, binary.BigEndian, &msg.ChunkSize) + + msg.Data = make([]byte, msg.ChunkSize) + binary.Read(buf, binary.BigEndian, msg.Data) + + return *msg, nil + +} + + +func (msg ChunkMsg) Bytes() []byte { + var buf bytes.Buffer + + buf.WriteByte(msg.MsgType()) + binary.Write(&buf, binary.BigEndian, msg.TimeStamp) + binary.Write(&buf, binary.BigEndian, msg.ChunkSize) + buf.Write(msg.Data) + + return buf.Bytes() +} + +func (msg ChunkMsg) Peer() uint32 { + return msg.PeerIdx +} + +func (msg ChunkMsg) MsgType() uint8 { + return MSGID_CHUNK_BODY +} + + + +type EndChunksMsg struct { + PeerIdx uint32 + TimeStamp int64 +} + +func NewChunksEndMsgFromBytes(b []byte, peerIdx uint32) (EndChunksMsg, error) { + + + msg := new(EndChunksMsg) + buf := bytes.NewBuffer(b[1:]) // get rid of messageType + + msg.PeerIdx = peerIdx + binary.Read(buf, binary.BigEndian, &msg.TimeStamp) + + return *msg, nil +} + +func (msg EndChunksMsg) Bytes() []byte { + var buf bytes.Buffer + + buf.WriteByte(msg.MsgType()) + binary.Write(&buf, binary.BigEndian, msg.TimeStamp) + + return buf.Bytes() +} + +func (msg EndChunksMsg) Peer() uint32 { + return msg.PeerIdx +} + +func (msg EndChunksMsg) MsgType() uint8 { + return MSGID_CHUNKS_END +} \ No newline at end of file diff --git a/qln/dlc.go b/qln/dlc.go index fd61c7846..40e4e7b5b 100644 --- a/qln/dlc.go +++ b/qln/dlc.go @@ -57,6 +57,10 @@ func (nd *LitNode) OfferDlc(peerIdx uint32, cIdx uint64) error { return fmt.Errorf("You need to set a coin type for the contract before offering it") } + if c.FeePerByte == dlc.FEEPERBYTE_NOT_SET { + return fmt.Errorf("You need to set a fee per byte for the contract before offering it") + } + if c.Division == nil { return fmt.Errorf("You need to set a payout division for the contract before offering it") } @@ -85,6 +89,16 @@ func (nd *LitNode) OfferDlc(peerIdx uint32, cIdx uint64) error { return err } + ourPayoutPKHKey, err := nd.GetUsePub(kg, UseContractPayoutPKH) + if err != nil { + logging.Errorf("Error while getting our payout pubkey: %s", err.Error()) + c.Status = lnutil.ContractStatusError + nd.DlcManager.SaveContract(c) + return err + } + + copy(c.OurPayoutPKH[:], btcutil.Hash160(ourPayoutPKHKey[:])) + // Fund the contract err = nd.FundContract(c) if err != nil { @@ -227,6 +241,7 @@ func (nd *LitNode) DlcOfferHandler(msg lnutil.DlcOfferMsg, peer *RemotePeer) { c.OurChangePKH = msg.Contract.TheirChangePKH c.TheirChangePKH = msg.Contract.OurChangePKH c.TheirIdx = msg.Contract.Idx + c.TheirPayoutPKH = msg.Contract.OurPayoutPKH c.Division = make([]lnutil.DlcContractDivision, len(msg.Contract.Division)) for i := 0; i < len(msg.Contract.Division); i++ { @@ -236,6 +251,7 @@ func (nd *LitNode) DlcOfferHandler(msg lnutil.DlcOfferMsg, peer *RemotePeer) { // Copy c.CoinType = msg.Contract.CoinType + c.FeePerByte = msg.Contract.FeePerByte c.OracleA = msg.Contract.OracleA c.OracleR = msg.Contract.OracleR c.OracleTimestamp = msg.Contract.OracleTimestamp @@ -324,6 +340,8 @@ func (nd *LitNode) DlcContractAckHandler(msg lnutil.DlcContractAckMsg, peer *Rem c.Status = lnutil.ContractStatusAcknowledged + c.TheirSettlementSignatures = msg.SettlementSignatures + err = nd.DlcManager.SaveContract(c) if err != nil { logging.Errorf("DlcContractAckHandler SaveContract err %s\n", err.Error()) @@ -474,18 +492,60 @@ func (nd *LitNode) BuildDlcFundingTransaction(c *lnutil.DlcContract) (wire.MsgTx var ourInputTotal int64 var theirInputTotal int64 + our_txin_num := 0 for _, u := range c.OurFundingInputs { - tx.AddTxIn(wire.NewTxIn(&u.Outpoint, nil, nil)) + txin := wire.NewTxIn(&u.Outpoint, nil, nil) + tx.AddTxIn(txin) ourInputTotal += u.Value + our_txin_num += 1 + } + + + their_txin_num := 0 for _, u := range c.TheirFundingInputs { - tx.AddTxIn(wire.NewTxIn(&u.Outpoint, nil, nil)) + txin := wire.NewTxIn(&u.Outpoint, nil, nil) + tx.AddTxIn(txin) theirInputTotal += u.Value + their_txin_num += 1 + } + + + //==================================================== + + // Here can be a situation when peers have different number of inputs. + // Therefore we have to calculate fees for each peer separately. + + // This transaction always will have 3 outputs ( 43 + 31 + 31) + tx_basesize := 10 + 43 + 31 + 31 + tx_size_foreach := tx_basesize / 2 + tx_size_foreach += 1 // rounding + + input_wit_size := 107 + + our_tx_vsize := uint32(((tx_size_foreach + (41 * our_txin_num)) * 3 + (tx_size_foreach + (41 * our_txin_num) + (input_wit_size*our_txin_num) )) / 4) + their_tx_vsize := uint32(((tx_size_foreach + (41 * their_txin_num)) * 3 + (tx_size_foreach + (41 * their_txin_num) + (input_wit_size*their_txin_num) )) / 4) + + //rounding + our_tx_vsize += 1 + their_tx_vsize += 1 + + + our_fee := int64(our_tx_vsize * c.FeePerByte) + their_fee := int64(their_tx_vsize * c.FeePerByte) + // add change and sort - tx.AddTxOut(wire.NewTxOut(theirInputTotal-c.TheirFundingAmount-500, lnutil.DirectWPKHScriptFromPKH(c.TheirChangePKH))) - tx.AddTxOut(wire.NewTxOut(ourInputTotal-c.OurFundingAmount-500, lnutil.DirectWPKHScriptFromPKH(c.OurChangePKH))) + + their_txout := wire.NewTxOut(theirInputTotal-c.TheirFundingAmount-their_fee, lnutil.DirectWPKHScriptFromPKH(c.TheirChangePKH)) + tx.AddTxOut(their_txout) + + + our_txout := wire.NewTxOut(ourInputTotal-c.OurFundingAmount-our_fee, lnutil.DirectWPKHScriptFromPKH(c.OurChangePKH)) + tx.AddTxOut(our_txout) + + txsort.InPlaceSort(tx) @@ -609,46 +669,77 @@ func (nd *LitNode) SettleContract(cIdx uint64, oracleValue int64, oracleSig [32] return [32]byte{}, [32]byte{}, err } - // TODO: Claim the contract settlement output back to our wallet - otherwise the peer can claim it after locktime. - txClaim := wire.NewMsgTx() - txClaim.Version = 2 - settleOutpoint := wire.OutPoint{Hash: settleTx.TxHash(), Index: 0} - txClaim.AddTxIn(wire.NewTxIn(&settleOutpoint, nil, nil)) + //=========================================== + // Claim TX + //=========================================== - addr, err := wal.NewAdr() - txClaim.AddTxOut(wire.NewTxOut(d.ValueOurs-1000, lnutil.DirectWPKHScriptFromPKH(addr))) // todo calc fee - fee is double here because the contract output already had the fee deducted in the settlement TX - kg.Step[2] = UseContractPayoutBase - privSpend, _ := wal.GetPriv(kg) + // Here the transaction size is always the same + // n := 8 + VarIntSerializeSize(uint64(len(msg.TxIn))) + + // VarIntSerializeSize(uint64(len(msg.TxOut))) + // n = 10 + // Plus Single input 41 + // Plus Single output 31 + // Plus 2 for all wittness transactions + // Plus Witness Data 151 - pubSpend := wal.GetPub(kg) - privOracle, pubOracle := koblitz.PrivKeyFromBytes(koblitz.S256(), oracleSig[:]) - privContractOutput := lnutil.CombinePrivateKeys(privSpend, privOracle) + // TxSize = 4 + 4 + 1 + 1 + 2 + 151 + 41 + 31 = 235 + // Vsize = ((235 - 151 - 2) * 3 + 235) / 4 = 120,25 - var pubOracleBytes [33]byte - copy(pubOracleBytes[:], pubOracle.SerializeCompressed()) - var pubSpendBytes [33]byte - copy(pubSpendBytes[:], pubSpend.SerializeCompressed()) - settleScript := lnutil.DlcCommitScript(c.OurPayoutBase, pubOracleBytes, c.TheirPayoutBase, 5) - err = nd.SignClaimTx(txClaim, settleTx.TxOut[0].Value, settleScript, privContractOutput, false) - if err != nil { - logging.Errorf("SettleContract SignClaimTx err %s", err.Error()) - return [32]byte{}, [32]byte{}, err - } + if ( d.ValueOurs != 0){ - // Claim TX should be valid here, so publish it. - err = wal.DirectSendTx(txClaim) - if err != nil { - logging.Errorf("SettleContract DirectSendTx (claim) err %s", err.Error()) - return [32]byte{}, [32]byte{}, err - } + vsize := uint32(121) + fee := vsize * c.FeePerByte + + // TODO: Claim the contract settlement output back to our wallet - otherwise the peer can claim it after locktime. + txClaim := wire.NewMsgTx() + txClaim.Version = 2 + + settleOutpoint := wire.OutPoint{Hash: settleTx.TxHash(), Index: 0} + txClaim.AddTxIn(wire.NewTxIn(&settleOutpoint, nil, nil)) + + addr, err := wal.NewAdr() + txClaim.AddTxOut(wire.NewTxOut(settleTx.TxOut[0].Value-int64(fee), lnutil.DirectWPKHScriptFromPKH(addr))) + + kg.Step[2] = UseContractPayoutBase + privSpend, _ := wal.GetPriv(kg) + + pubSpend := wal.GetPub(kg) + privOracle, pubOracle := koblitz.PrivKeyFromBytes(koblitz.S256(), oracleSig[:]) + privContractOutput := lnutil.CombinePrivateKeys(privSpend, privOracle) + + var pubOracleBytes [33]byte + copy(pubOracleBytes[:], pubOracle.SerializeCompressed()) + var pubSpendBytes [33]byte + copy(pubSpendBytes[:], pubSpend.SerializeCompressed()) + + settleScript := lnutil.DlcCommitScript(c.OurPayoutBase, pubOracleBytes, c.TheirPayoutBase, 5) + err = nd.SignClaimTx(txClaim, settleTx.TxOut[0].Value, settleScript, privContractOutput, false) + if err != nil { + logging.Errorf("SettleContract SignClaimTx err %s", err.Error()) + return [32]byte{}, [32]byte{}, err + } + + // Claim TX should be valid here, so publish it. + err = wal.DirectSendTx(txClaim) + if err != nil { + logging.Errorf("SettleContract DirectSendTx (claim) err %s", err.Error()) + return [32]byte{}, [32]byte{}, err + } + + c.Status = lnutil.ContractStatusClosed + err = nd.DlcManager.SaveContract(c) + if err != nil { + return [32]byte{}, [32]byte{}, err + } + return settleTx.TxHash(), txClaim.TxHash(), nil + + }else{ + + return settleTx.TxHash(), [32]byte{}, nil - c.Status = lnutil.ContractStatusClosed - err = nd.DlcManager.SaveContract(c) - if err != nil { - return [32]byte{}, [32]byte{}, err } - return settleTx.TxHash(), txClaim.TxHash(), nil + } diff --git a/qln/msghandler.go b/qln/msghandler.go index d1e980c50..88b1aa35f 100644 --- a/qln/msghandler.go +++ b/qln/msghandler.go @@ -574,8 +574,24 @@ func (nd *LitNode) HandleContractOPEvent(c *lnutil.DlcContract, if err != nil { return err } - txClaim.AddTxOut(wire.NewTxOut(value-500, - lnutil.DirectWPKHScriptFromPKH(addr))) // todo calc fee + + // Here the transaction size is always the same + // n := 8 + VarIntSerializeSize(uint64(len(msg.TxIn))) + + // VarIntSerializeSize(uint64(len(msg.TxOut))) + // n = 10 + // Plus Single input 41 + // Plus Single output 31 + // Plus 2 for all wittness transactions + // Plus Witness Data 108 + + // TxSize = 4 + 4 + 1 + 1 + 2 + 108 + 41 + 31 = 192 + // Vsize = ((192 - 108 - 2) * 3 + 192) / 4 = 109,5 + + vsize := uint32(110) + fee := vsize * c.FeePerByte + + txClaim.AddTxOut(wire.NewTxOut(value-int64(fee), + lnutil.DirectWPKHScriptFromPKH(addr))) var kg portxo.KeyGen kg.Depth = 5 diff --git a/test/itest_dlc.py b/test/itest_dlc.py new file mode 100644 index 000000000..7cfc66975 --- /dev/null +++ b/test/itest_dlc.py @@ -0,0 +1,851 @@ +import testlib + +import time, datetime +import json + +import pprint + +import requests # pip3 install requests + +import codecs + +deb_mod = False + +def run_t(env, params): + global deb_mod + try: + + lit_funding_amt = params[0] + contract_funding_amt = params[1] + oracle_value = params[2] + node_to_settle = params[3] + valueFullyOurs=params[4] + valueFullyTheirs=params[5] + + FundingTxVsize = params[6][0] + SettlementTxVsize = params[6][1] + + feeperbyte = params[7] + + SetTxFeeOurs = params[8] + SetTxFeeTheirs = params[9] + + ClaimTxFeeOurs = params[10] + ClaimTxFeeTheirs = params[11] + + bc = env.bitcoind + + #------------ + # Create oracles + #------------ + + env.new_oracle(1, oracle_value) # publishing interval is 1 second. + + #settle_lit = env.lits[node_to_settle] + + oracle1 = env.oracles[0] + + time.sleep(2) + + #------------ + # Create lits + #------------ + + lit1 = env.lits[0] + lit2 = env.lits[1] + + + pp = pprint.PrettyPrinter(indent=4) + + + #------------------------------------------ + if deb_mod: + print("ADDRESSES BEFORE SEND TO ADDRESS") + print("LIT1 Addresses") + print(pp.pprint(lit1.rpc.GetAddresses())) + + print("LIT2 Addresses") + print(pp.pprint(lit2.rpc.GetAddresses())) + + print("bitcoind Addresses") + print(pp.pprint(bc.rpc.listaddressgroupings())) + #------------------------------------------ + + + lit1.connect_to_peer(lit2) + print("---------------") + print('Connecting lit1:', lit1.lnid, 'to lit2:', lit2.lnid) + + addr1 = lit1.make_new_addr() + txid1 = bc.rpc.sendtoaddress(addr1, lit_funding_amt) + + if deb_mod: + print("Funding TxId lit1: " + str(txid1)) + + time.sleep(5) + + addr2 = lit2.make_new_addr() + txid2 = bc.rpc.sendtoaddress(addr2, lit_funding_amt) + + if deb_mod: + print("Funding TxId lit2: " + str(txid2)) + + time.sleep(5) + + env.generate_block() + time.sleep(5) + + print("Funding") + bals1 = lit1.get_balance_info() + print('new lit1 balance:', bals1['TxoTotal'], 'in txos,', bals1['ChanTotal'], 'in chans') + bal1sum = bals1['TxoTotal'] + bals1['ChanTotal'] + print(' = sum ', bal1sum) + + print(lit_funding_amt) + + lit_funding_amt *= 100000000 # to satoshi + + + + + bals2 = lit2.get_balance_info() + print('new lit2 balance:', bals2['TxoTotal'], 'in txos,', bals2['ChanTotal'], 'in chans') + bal2sum = bals2['TxoTotal'] + bals2['ChanTotal'] + print(' = sum ', bal2sum) + + + assert bal1sum == lit_funding_amt, "Funding lit1 does not works" + assert bal2sum == lit_funding_amt, "Funding lit2 does not works" + + + #------------------------------------------ + if deb_mod: + print("ADDRESSES AFTER SEND TO ADDRESS") + print("LIT1 Addresses") + print(pp.pprint(lit1.rpc.GetAddresses())) + + print("LIT2 Addresses") + print(pp.pprint(lit2.rpc.GetAddresses())) + + print("bitcoind Addresses") + print(pp.pprint(bc.rpc.listaddressgroupings())) + #------------------------------------------ + + + # #------------ + # # Add oracles + # #------------ + + res = lit1.rpc.ListOracles() + assert len(res) != 0, "Initial lis of oracles must be empty" + + oracle1_pubkey = json.loads(oracle1.get_pubkey()) + assert len(oracle1_pubkey["A"]) == 66, "Wrong oracle1 pub key" + + # oracle2_pubkey = json.loads(oracle2.get_pubkey()) + # assert len(oracle2_pubkey["A"]) == 66, "Wrong oracle2 pub key" + + oracle_res1 = lit1.rpc.AddOracle(Key=oracle1_pubkey["A"], Name="oracle1") + assert oracle_res1["Oracle"]["Idx"] == 1, "AddOracle does not works" + + res = lit1.rpc.ListOracles(ListOraclesArgs={}) + assert len(res["Oracles"]) == 1, "ListOracles 1 does not works" + + + lit2.rpc.AddOracle(Key=oracle1_pubkey["A"], Name="oracle1") + + + # #------------ + # # Now we have to create a contract in the lit1 node. + # #------------ + + contract = lit1.rpc.NewContract() + + res = lit1.rpc.ListContracts() + assert len(res["Contracts"]) == 1, "ListContracts does not works" + + + res = lit1.rpc.GetContract(Idx=1) + assert res["Contract"]["Idx"] == 1, "GetContract does not works" + + + res = lit1.rpc.SetContractOracle(CIdx=contract["Contract"]["Idx"], OIdx=oracle_res1["Oracle"]["Idx"]) + assert res["Success"], "SetContractOracle does not works" + + datasources = json.loads(oracle1.get_datasources()) + + # Since the oracle publishes data every 1 second (we set this time above), + # we increase the time for a point by 3 seconds. + + settlement_time = int(time.time()) + 3 + + # dlc contract settime 1 1552080600 + lit1.rpc.SetContractSettlementTime(CIdx=contract["Contract"]["Idx"], Time=settlement_time) + + res = lit1.rpc.ListContracts() + assert res["Contracts"][contract["Contract"]["Idx"] - 1]["OracleTimestamp"] == settlement_time, "SetContractSettlementTime does not match settlement_time" + + rpoint1 = oracle1.get_rpoint(datasources[0]["id"], settlement_time) + + decode_hex = codecs.getdecoder("hex_codec") + b_RPoint = decode_hex(json.loads(rpoint1)['R'])[0] + RPoint = [elem for elem in b_RPoint] + + res = lit1.rpc.SetContractRPoint(CIdx=contract["Contract"]["Idx"], RPoint=RPoint) + assert res["Success"], "SetContractRpoint does not works" + + lit1.rpc.SetContractCoinType(CIdx=contract["Contract"]["Idx"], CoinType = 257) + res = lit1.rpc.GetContract(Idx=contract["Contract"]["Idx"]) + assert res["Contract"]["CoinType"] == 257, "SetContractCoinType does not works" + + + lit1.rpc.SetContractFeePerByte(CIdx=contract["Contract"]["Idx"], FeePerByte = feeperbyte) + res = lit1.rpc.GetContract(Idx=contract["Contract"]["Idx"]) + assert res["Contract"]["FeePerByte"] == feeperbyte, "SetContractFeePerByte does not works" + + ourFundingAmount = contract_funding_amt + theirFundingAmount = contract_funding_amt + + lit1.rpc.SetContractFunding(CIdx=contract["Contract"]["Idx"], OurAmount=ourFundingAmount, TheirAmount=theirFundingAmount) + res = lit1.rpc.GetContract(Idx=contract["Contract"]["Idx"]) + assert res["Contract"]["OurFundingAmount"] == ourFundingAmount, "SetContractFunding does not works" + assert res["Contract"]["TheirFundingAmount"] == theirFundingAmount, "SetContractFunding does not works" + + print("Before SetContractDivision") + + res = lit1.rpc.SetContractDivision(CIdx=contract["Contract"]["Idx"], ValueFullyOurs=valueFullyOurs, ValueFullyTheirs=valueFullyTheirs) + assert res["Success"], "SetContractDivision does not works" + + print("After SetContractDivision") + + time.sleep(8) + + + res = lit1.rpc.ListConnections() + print(res) + + print("Before OfferContract") + + res = lit1.rpc.OfferContract(CIdx=contract["Contract"]["Idx"], PeerIdx=lit1.get_peer_id(lit2)) + assert res["Success"], "OfferContract does not works" + + print("After OfferContract") + + time.sleep(8) + + + print("Before ContractRespond") + + res = lit2.rpc.ContractRespond(AcceptOrDecline=True, CIdx=1) + assert res["Success"], "ContractRespond on lit2 does not works" + + print("After ContractRespond") + + time.sleep(8) + + #------------------------------------------ + + if deb_mod: + print("ADDRESSES AFTER CONTRACT RESPOND") + print("LIT1 Addresses") + print(lit1.rpc.GetAddresses()) + + print("LIT2 Addresses") + print(lit2.rpc.GetAddresses()) + + print("bitcoind Addresses") + print(bc.rpc.listaddressgroupings()) + + + # #------------------------------------------ + + + print("Before Generate Block") + + env.generate_block() + time.sleep(5) + + print("After Generate Block") + + print("Accept") + bals1 = lit1.get_balance_info() + print('new lit1 balance:', bals1['TxoTotal'], 'in txos,', bals1['ChanTotal'], 'in chans') + bal1sum = bals1['TxoTotal'] + bals1['ChanTotal'] + print(' = sum ', bal1sum) + + lit1_bal_after_accept = (lit_funding_amt - ourFundingAmount) - (126*feeperbyte) + + + bals2 = lit2.get_balance_info() + print('new lit2 balance:', bals2['TxoTotal'], 'in txos,', bals2['ChanTotal'], 'in chans') + bal2sum = bals2['TxoTotal'] + bals2['ChanTotal'] + print(' = sum ', bal2sum) + + lit2_bal_after_accept = (lit_funding_amt - theirFundingAmount) - (126*feeperbyte) + + + assert bal1sum == lit1_bal_after_accept, "lit1 Balance after contract accept does not match" + assert bal2sum == lit2_bal_after_accept, "lit2 Balance after contract accept does not match" + + oracle1_val = "" + oracle1_sig = "" + + i = 0 + while True: + res = oracle1.get_publication(json.loads(rpoint1)['R']) + time.sleep(5) + i += 1 + if i>4: + assert False, "Error: Oracle does not publish data" + + try: + oracle1_val = json.loads(res)["value"] + oracle1_sig = json.loads(res)["signature"] + break + except BaseException as e: + print(e) + next + + + b_OracleSig = decode_hex(oracle1_sig)[0] + OracleSig = [elem for elem in b_OracleSig] + + + print("Before SettleContract") + time.sleep(8) + + + res = env.lits[node_to_settle].rpc.SettleContract(CIdx=contract["Contract"]["Idx"], OracleValue=oracle1_val, OracleSig=OracleSig) + assert res["Success"], "SettleContract does not works." + + time.sleep(8) + + print('After SettleContract:') + print(res) + + + try: + env.generate_block(1) + time.sleep(2) + env.generate_block(10) + time.sleep(2) + env.generate_block(1) + except BaseException as be: + print("Exception After SettleContract: ") + print(be) + + time.sleep(10) + #------------------------------------------ + + if deb_mod: + + best_block_hash = bc.rpc.getbestblockhash() + bb = bc.rpc.getblock(best_block_hash) + print(bb) + print("bb['height']: " + str(bb['height'])) + + print("Balance from RPC: " + str(bc.rpc.getbalance())) + + # batch support : print timestamps of blocks 0 to 99 in 2 RPC round-trips: + commands = [ [ "getblockhash", height] for height in range(bb['height'] + 1) ] + block_hashes = bc.rpc.batch_(commands) + blocks = bc.rpc.batch_([ [ "getblock", h ] for h in block_hashes ]) + block_times = [ block["time"] for block in blocks ] + print(block_times) + + print('--------------------') + + for b in blocks: + print("--------BLOCK--------") + print(b) + tx = b["tx"] + #print(tx) + try: + + for i in range(len(tx)): + print("--------TRANSACTION--------") + rtx = bc.rpc.getrawtransaction(tx[i]) + print(rtx) + decoded = bc.rpc.decoderawtransaction(rtx) + pp.pprint(decoded) + except BaseException as be: + print(be) + # print(type(rtx)) + print('--------') + + + #------------------------------------------ + #------------------------------------------ + print("AFter Settle") + + print("ORACLE VALUE:", oracle1_val, "; oracle signature:", oracle1_sig) + + valueOurs = 0 + + + valueOurs = env.lits[node_to_settle].rpc.GetContractDivision(CIdx=contract["Contract"]["Idx"],OracleValue=oracle1_val)['ValueOurs'] + valueTheirs = contract_funding_amt * 2 - valueOurs + + print("valueOurs:", valueOurs, "; valueTheirs:", valueTheirs) + + + lit1_bal_after_settle = valueOurs - SetTxFeeOurs + lit2_bal_after_settle = valueTheirs - SetTxFeeTheirs + + + lit1_bal_after_claim = lit1_bal_after_settle - ClaimTxFeeOurs + lit2_bal_after_claim = lit2_bal_after_settle - ClaimTxFeeTheirs + + lit1_bal_result = lit1_bal_after_claim + lit1_bal_after_accept + lit2_bal_result = lit2_bal_after_claim + lit2_bal_after_accept + + print("============== Fees Calc ===========================") + print("lit1_bal_after_settle", lit1_bal_after_settle) + print("lit2_bal_after_settle", lit2_bal_after_settle) + + print("lit1_bal_after_claim",lit1_bal_after_claim) + print("lit2_bal_after_claim",lit2_bal_after_claim) + + print("lit1_bal_result: ", lit1_bal_result) + print("lit2_bal_result: ", lit2_bal_result) + print("====================================================") + + + + bals1 = lit1.get_balance_info() + print('new lit1 balance:', bals1['TxoTotal'], 'in txos,', bals1['ChanTotal'], 'in chans') + bal1sum = bals1['TxoTotal'] + bals1['ChanTotal'] + print(bals1) + print(' = sum ', bal1sum) + + + bals2 = lit2.get_balance_info() + print('new lit2 balance:', bals2['TxoTotal'], 'in txos,', bals2['ChanTotal'], 'in chans') + bal2sum = bals2['TxoTotal'] + bals2['ChanTotal'] + print(bals2) + print(' = sum ', bal2sum) + + if node_to_settle == 0: + assert bal1sum == lit1_bal_result, "The resulting lit1 node balance does not match." + assert bal2sum == lit2_bal_result, "The resulting lit2 node balance does not match." + elif node_to_settle == 1: + assert bal1sum == lit2_bal_result, "The resulting lit1 node balance does not match." + assert bal2sum == lit1_bal_result, "The resulting lit2 node balance does not match." + + + + + #------------------------------------------ + if deb_mod: + print("ADDRESSES AFTER SETTLE") + print("LIT1 Addresses") + print(pp.pprint(lit1.rpc.GetAddresses())) + + print("LIT2 Addresses") + print(pp.pprint(lit2.rpc.GetAddresses())) + + print("bitcoind Addresses") + print(pp.pprint(bc.rpc.listaddressgroupings())) + #------------------------------------------ + + + + print("=====START CONTRACT N1=====") + res = lit1.rpc.ListContracts() + #print(pp.pprint(res)) + print(res) + print("=====END CONTRACT N1=====") + + print("=====START CONTRACT N2=====") + res = lit2.rpc.ListContracts() + #print(pp.pprint(res)) + print(res) + print("=====END CONTRACT N2=====") + + + + except BaseException as be: + raise be + + + +def t_11_0(env): + + #----------------------------- + # 1)Funding transaction. + # Here can be a situation when peers have different number of inputs. + + # ::lit1:: BuildDlcFundingTransaction: qln/dlc.go: our_tx_vsize: 126 + # ::lit1:: BuildDlcFundingTransaction: qln/dlc.go: their_tx_vsize: 126 + # ::lit1:: BuildDlcFundingTransaction: qln/dlc.go: our_fee: 10080 + # ::lit1:: BuildDlcFundingTransaction: qln/dlc.go: their_fee: 10080 + + # Vsize from Blockchain: 252 + + # So we expect lit1, and lit2 balances equal to 89989920 !!! + # 90000000 - 89989920 = 10080 + # But this is only when peers have one input each. What we expect. + + #----------------------------- + # 2) SettlementTx vsize will be printed + + # ::lit0:: SettlementTx()1: qln/dlclib.go: --------------------: + # ::lit0:: SettlementTx()1: qln/dlclib.go: valueOurs: 18000000 + # ::lit0:: SettlementTx()1: qln/dlclib.go: valueTheirs: 2000000 + # ::lit0:: SettlementTx()1: qln/dlclib.go: --------------------: + # ::lit0:: SettlementTx()2: qln/dlclib.go: --------------------: + # ::lit0:: SettlementTx()2: qln/dlclib.go: valueOurs: 18000000 + # ::lit0:: SettlementTx()2: qln/dlclib.go: valueTheirs: 2000000 + # ::lit0:: SettlementTx()2: qln/dlclib.go: --------------------: + # ::lit0:: SettlementTx()3: qln/dlclib.go: --------------------: + # ::lit0:: SettlementTx()3: qln/dlclib.go: totalFee: 14400 + # ::lit0:: SettlementTx()3: qln/dlclib.go: feeEach: 7200 + # ::lit0:: SettlementTx()3: qln/dlclib.go: feeOurs: 7200 + # ::lit0:: SettlementTx()3: qln/dlclib.go: feeTheirs: 7200 + # ::lit0:: SettlementTx()3: qln/dlclib.go: valueOurs: 17992800 + # ::lit0:: SettlementTx()3: qln/dlclib.go: valueTheirs: 1992800 + # ::lit0:: SettlementTx()3: qln/dlclib.go: vsize: 0 # Actually we have 14400/80 = 180 + # ::lit0:: SettlementTx()3: qln/dlclib.go: --------------------: + + + # Vsize from Blockchain: 181 + + # There fore we expect here + # valueOurs: 17992800 = 18000000 - 7200 !!! + # valueTheirs: 1992800 = 2000000 - 7200 !!! + + + #----------------------------- + + # 3) Claim TX in SettleContract + # Here the transaction vsize is always the same: 121 + + + # Vsize from Blockchain: 121 + + #----------------------------- + + # 4) Claim TX from another peer + # Here the transaction vsize is always the same: 110 + + # Vsize from Blockchain: 110 + + #----------------------------- + + # 17992800 - (121 * 80) = 17983120 + # 89989920 + 17983120 = 107973040 + + # 1992800 - (110*80) = 1984000 + # 89989920 + 1984000 = 91973920 + + #----------------------------- + + # AFter Settle + # new lit1 balance: 107973040 in txos, 0 in chans + # = sum 107973040 + # {'CoinType': 257, 'SyncHeight': 514, 'ChanTotal': 0, 'TxoTotal': 107973040, 'MatureWitty': 107973040, 'FeeRate': 80} + # new lit2 balance: 91973920 in txos, 0 in chans + # = sum 91973920 + + #----------------------------- + + oracle_value = 11 + node_to_settle = 0 + + valueFullyOurs=10 + valueFullyTheirs=20 + + lit_funding_amt = 1 # 1 BTC + contract_funding_amt = 10000000 # satoshi + + FundingTxVsize = 252 + SettlementTxVsize = 180 + + SetTxFeeOurs = 7200 + SetTxFeeTheirs = 7200 + + ClaimTxFeeOurs = 121 * 80 + ClaimTxFeeTheirs = 110 * 80 + + + feeperbyte = 80 + + + vsizes = [FundingTxVsize, SettlementTxVsize] + + params = [lit_funding_amt, contract_funding_amt, oracle_value, node_to_settle, valueFullyOurs, valueFullyTheirs, vsizes, feeperbyte, SetTxFeeOurs, SetTxFeeTheirs, ClaimTxFeeOurs, ClaimTxFeeTheirs] + + run_t(env, params) + + + +# ==================================================================================== +# ==================================================================================== + + +def t_1300_1(env): + + #----------------------------- + # 1)Funding transaction. + # Here can be a situation when peers have different number of inputs. + + # ::lit1:: BuildDlcFundingTransaction: qln/dlc.go: our_tx_vsize: 126 + # ::lit1:: BuildDlcFundingTransaction: qln/dlc.go: their_tx_vsize: 126 + # ::lit1:: BuildDlcFundingTransaction: qln/dlc.go: our_fee: 10080 + # ::lit1:: BuildDlcFundingTransaction: qln/dlc.go: their_fee: 10080 + + # Vsize from Blockchain: 252 + + # So we expect lit1, and lit2 balances equal to 89989920 !!! + # 90000000 - 89989920 = 10080 + # But this is only when peers have one input each. What we expect. + + #----------------------------- + # 2) SettlementTx vsize will be printed + + # ::lit1:: SettlementTx()1: qln/dlclib.go: --------------------: + # ::lit1:: SettlementTx()1: qln/dlclib.go: valueOurs: 6000000 + # ::lit1:: SettlementTx()1: qln/dlclib.go: valueTheirs: 14000000 + # ::lit1:: SettlementTx()1: qln/dlclib.go: --------------------: + # ::lit1:: SettlementTx()2: qln/dlclib.go: --------------------: + # ::lit1:: SettlementTx()2: qln/dlclib.go: valueOurs: 5992800 + # ::lit1:: SettlementTx()2: qln/dlclib.go: valueTheirs: 13992800 + # ::lit1:: SettlementTx()2: qln/dlclib.go: --------------------: + # ::lit1:: SettlementTx()2: qln/dlclib.go: --------------------: + # ::lit1:: SettlementTx()2: qln/dlclib.go: totalFee: 14400 + # ::lit1:: SettlementTx()2: qln/dlclib.go: feeEach: 7200 + # ::lit1:: SettlementTx()2: qln/dlclib.go: feeOurs: 7200 + # ::lit1:: SettlementTx()2: qln/dlclib.go: feeTheirs: 7200 + # ::lit1:: SettlementTx()2: qln/dlclib.go: valueOurs: 5992800 + # ::lit1:: SettlementTx()2: qln/dlclib.go: valueTheirs: 13992800 + # ::lit1:: SettlementTx()2: qln/dlclib.go: vsize: 180 + # ::lit1:: SettlementTx()2: qln/dlclib.go: --------------------: + + + # Vsize from Blockchain: 181 + + # There fore we expect here + # valueOurs: 5992800 = 6000000 - 7200 !!! + # valueTheirs: 13992800 = 14000000 - 7200 !!! + + + #----------------------------- + + # 3) Claim TX in SettleContract lit1 + # Here the transaction vsize is always the same: 121 + + + # Vsize from Blockchain: 121 + + #----------------------------- + + # 4) Claim TX from another peer lit0 + # Here the transaction vsize is always the same: 110 + + # Vsize from Blockchain: 110 + + #----------------------------- + + # 5992800 - (121 * 80) = 5983120 + # 89989920 + 5983120 = 95973040 + + # 13992800 - (110*80) = 13984000 + # 89989920 + 13984000 = 103973920 + + #----------------------------- + + # AFter Settle + # new lit1 balance: 103973920 in txos, 0 in chans + # = sum 103973920 + + + # new lit2 balance: 95973040 in txos, 0 in chans + # = sum 95973040 + + #----------------------------- + + oracle_value = 1300 + node_to_settle = 1 + + valueFullyOurs=1000 + valueFullyTheirs=2000 + + lit_funding_amt = 1 # 1 BTC + contract_funding_amt = 10000000 # satoshi + + FundingTxVsize = 252 + SettlementTxVsize = 180 + + + SetTxFeeOurs = 7200 + SetTxFeeTheirs = 7200 + + ClaimTxFeeOurs = 121 * 80 + ClaimTxFeeTheirs = 110 * 80 + + + feeperbyte = 80 + + + vsizes = [FundingTxVsize, SettlementTxVsize] + + params = [lit_funding_amt, contract_funding_amt, oracle_value, node_to_settle, valueFullyOurs, valueFullyTheirs, vsizes, feeperbyte, SetTxFeeOurs, SetTxFeeTheirs, ClaimTxFeeOurs, ClaimTxFeeTheirs] + + run_t(env, params) + + + +# ==================================================================================== +# ==================================================================================== + + + +def t_10_0(env): + + + oracle_value = 10 + node_to_settle = 0 + + valueFullyOurs=10 + valueFullyTheirs=20 + + lit_funding_amt = 1 # 1 BTC + contract_funding_amt = 10000000 # satoshi + + FundingTxVsize = 252 + SettlementTxVsize = 150 + + SetTxFeeOurs = 150 * 80 + SetTxFeeTheirs = 0 + + ClaimTxFeeOurs = 121 * 80 + ClaimTxFeeTheirs = 0 + + + feeperbyte = 80 + + + vsizes = [FundingTxVsize, SettlementTxVsize] + + params = [lit_funding_amt, contract_funding_amt, oracle_value, node_to_settle, valueFullyOurs, valueFullyTheirs, vsizes, feeperbyte, SetTxFeeOurs, SetTxFeeTheirs, ClaimTxFeeOurs, ClaimTxFeeTheirs] + + run_t(env, params) + + + +# ==================================================================================== +# ==================================================================================== + + + +def t_10_1(env): + + + oracle_value = 10 + node_to_settle = 1 + + valueFullyOurs=10 + valueFullyTheirs=20 + + lit_funding_amt = 1 # 1 BTC + contract_funding_amt = 10000000 # satoshi + + FundingTxVsize = 252 + SettlementTxVsize = 137 + + + + SetTxFeeOurs = 0 + SetTxFeeTheirs = 137 * 80 + + ClaimTxFeeOurs = 0 + ClaimTxFeeTheirs = 110 * 80 + + + feeperbyte = 80 + + + vsizes = [FundingTxVsize, SettlementTxVsize] + + params = [lit_funding_amt, contract_funding_amt, oracle_value, node_to_settle, valueFullyOurs, valueFullyTheirs, vsizes, feeperbyte, SetTxFeeOurs, SetTxFeeTheirs, ClaimTxFeeOurs, ClaimTxFeeTheirs] + + run_t(env, params) + + + +# ==================================================================================== +# ==================================================================================== + + + +def t_20_0(env): + + + oracle_value = 20 + node_to_settle = 0 + + valueFullyOurs=10 + valueFullyTheirs=20 + + lit_funding_amt = 1 # 1 BTC + contract_funding_amt = 10000000 # satoshi + + FundingTxVsize = 252 + SettlementTxVsize = 137 + + SetTxFeeOurs = 0 + SetTxFeeTheirs = 137 * 80 + + ClaimTxFeeOurs = 0 + ClaimTxFeeTheirs = 110 * 80 + + + feeperbyte = 80 + + + vsizes = [FundingTxVsize, SettlementTxVsize] + + params = [lit_funding_amt, contract_funding_amt, oracle_value, node_to_settle, valueFullyOurs, valueFullyTheirs, vsizes, feeperbyte, SetTxFeeOurs, SetTxFeeTheirs, ClaimTxFeeOurs, ClaimTxFeeTheirs] + + run_t(env, params) + + + + +# ==================================================================================== +# ==================================================================================== + + + +def t_20_1(env): + + + oracle_value = 20 + node_to_settle = 1 + + valueFullyOurs=10 + valueFullyTheirs=20 + + lit_funding_amt = 1 # 1 BTC + contract_funding_amt = 10000000 # satoshi + + FundingTxVsize = 252 + SettlementTxVsize = 150 + + + + SetTxFeeOurs = 150 * 80 + SetTxFeeTheirs = 0 + + ClaimTxFeeOurs = 121 * 80 + ClaimTxFeeTheirs = 0 + + + feeperbyte = 80 + + + vsizes = [FundingTxVsize, SettlementTxVsize] + + params = [lit_funding_amt, contract_funding_amt, oracle_value, node_to_settle, valueFullyOurs, valueFullyTheirs, vsizes, feeperbyte, SetTxFeeOurs, SetTxFeeTheirs, ClaimTxFeeOurs, ClaimTxFeeTheirs] + + run_t(env, params) diff --git a/test/itest_send.py b/test/itest_send.py index 780ddb93b..fcc896f1f 100644 --- a/test/itest_send.py +++ b/test/itest_send.py @@ -38,5 +38,5 @@ def run_test(env): # Validate. bal2 = bc.rpc.getbalance() print('bitcoind balance:', bal0, '->', bal1, '->', bal2, '(', bal2 - bal1, ')') - if bal2 != bal1 + 12.5 + 0.5: # the 12.5 is because we mined a block + if float(bal2) != float(bal1) + 12.5 + 0.5: # the 12.5 is because we mined a block raise AssertionError("Balance in bitcoind doesn't match what we think it should be!") diff --git a/test/runtests.py b/test/runtests.py index 441c6d656..2f1303e1b 100755 --- a/test/runtests.py +++ b/test/runtests.py @@ -73,7 +73,7 @@ def load_tests_from_file(path): 'name': tname, 'pretty_name': pretty, 'test_func': tfn, - 'node_cnt': t['node_cnt'] + 'node_cnt': t['node_cnt'], }) return tests diff --git a/test/testlib.py b/test/testlib.py index e1d55757f..2be516382 100644 --- a/test/testlib.py +++ b/test/testlib.py @@ -6,10 +6,17 @@ import logging import random import shutil +import json import testutil -import btcrpc import litrpc +import requests + + +from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException + + +# The dlcoracle binary must be accessible throught a PATH variable. LIT_BIN = "%s/../lit" % paths.abspath(paths.dirname(__file__)) @@ -53,6 +60,8 @@ def get_new_id(): next_id += 1 return id + + class LitNode(): def __init__(self, bcnode): self.bcnode = bcnode @@ -88,7 +97,7 @@ def start(self): # Now figure out the args to use and then start Lit. args = [ LIT_BIN, - "-vv", + #"-vv", "--reg", "127.0.0.1:" + str(self.bcnode.p2p_port), "--tn3", "", # disable autoconnect "--dir", self.data_dir, @@ -119,6 +128,7 @@ def get_sync_height(self): for bal in self.rpc.balance(): if bal['CoinType'] == REGTEST_COINTYPE: return bal['SyncHeight'] + print("return -1") return -1 def connect_to_peer(self, other): @@ -194,15 +204,19 @@ def __init__(self): "-rpcuser=rpcuser", "-rpcpassword=rpcpass", "-rpcport=" + str(self.rpc_port), + "-txindex" ] + self.proc = subprocess.Popen(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + # Make the RPC client for it. testutil.wait_until_port("localhost", self.rpc_port) testutil.wait_until_port("localhost", self.p2p_port) - self.rpc = btcrpc.BtcClient("localhost", self.rpc_port, "rpcuser", "rpcpass") + + self.rpc = AuthServiceProxy("http://%s:%s@127.0.0.1:%s"%("rpcuser", "rpcpass", self.rpc_port), timeout=240) # Make sure that we're actually ready to accept RPC calls. def ck_ready(): @@ -234,10 +248,85 @@ def shutdown(self): else: pass # do nothing I guess? +class OracleNode(): + def __init__(self, interval, valueToPublish): + + self.data_dir = new_data_dir("oracle") + self.httpport = str(new_port()) + self.interval = str(interval) + self.valueToPublish = str(valueToPublish) + + # Write a hexkey to the privkey file + with open(paths.join(self.data_dir, "privkey.hex"), 'w+') as f: + s = '' + for _ in range(192): + s += hexchars[random.randint(0, len(hexchars) - 1)] + print('Using key:', s) + f.write(s + "\n") + + self.start() + + + def start(self): + + # See if we should print stdout + outputredir = subprocess.DEVNULL + ev_output_show = os.getenv("ORACLE_OUTPUT_SHOW", default="0") + if ev_output_show == "1": + outputredir = None + + # Now figure out the args to use and then start Lit. + args = [ + "dlcoracle", + "--DataDir="+self.data_dir, + "--HttpPort=" + self.httpport, + "--Interval=" + self.interval, + "--ValueToPublish=" + self.valueToPublish, + ] + + penv = os.environ.copy() + + self.proc = subprocess.Popen(args, + stdin=subprocess.DEVNULL, + stdout=outputredir, + stderr=outputredir, + env=penv) + + def shutdown(self): + if self.proc is not None: + self.proc.kill() + self.proc.wait() + self.proc = None + else: + pass # do nothing I guess? + + shutil.rmtree(self.data_dir) + + def get_pubkey(self): + res = requests.get("http://localhost:"+self.httpport+"/api/pubkey") + return res.text + + def get_datasources(self): + res = requests.get("http://localhost:"+self.httpport+"/api/datasources") + return res.text + + + def get_rpoint(self, datasourceID, unixTime): + res = requests.get("http://localhost:"+self.httpport+"/api/rpoint/" + str(datasourceID) + "/" + str(unixTime)) + print("get_rpoint:", "http://localhost:"+self.httpport+"/api/rpoint/" + str(datasourceID) + "/" + str(unixTime)) + print(res.text) + return res.text + + def get_publication(self, rpoint): + res = requests.get("http://localhost:"+self.httpport+"/api/publication/" + rpoint) + return res.text + + class TestEnv(): def __init__(self, litcnt): logger.info("starting nodes...") self.bitcoind = BitcoinNode() + self.oracles = [] self.lits = [] for i in range(litcnt): node = LitNode(self.bitcoind) @@ -260,6 +349,12 @@ def new_lit_node(self): self.generate_block(count=0) # Force it to wait for sync. return node + def new_oracle(self, interval, valueToPublish): + oracle = OracleNode(interval, valueToPublish) + self.oracles.append(oracle) + return oracle + + def generate_block(self, count=1): if count > 0: self.bitcoind.rpc.generate(count) @@ -279,6 +374,8 @@ def shutdown(self): for l in self.lits: l.shutdown() self.bitcoind.shutdown() + for o in self.oracles: + o.shutdown() def clean_data_dir(): datadir = get_root_data_dir() diff --git a/test/tests.txt b/test/tests.txt index 08fc62242..bf0ed801a 100644 --- a/test/tests.txt +++ b/test/tests.txt @@ -23,3 +23,24 @@ pushbreak 2 forward pushbreak 2 reverse pushclose 2 forward pushclose 2 reverse + +# DLC subsystem tx sizes calculation, etc... + +# regular contract +dlc 2 t_11_0 + +# large contract to test updeted messaging subsystem +# and also to test settlement from counterparty +dlc 2 t_1300_1 + +# test at left edge +dlc 2 t_10_0 + +# test at left edge from the counterparty +dlc 2 t_10_1 + +# test at right edge +dlc 2 t_20_0 + +# test at right edge from the counterparty +dlc 2 t_20_1